Home | History | Annotate | Download | only in store
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.mail.store;
     18 
     19 import android.content.Context;
     20 import android.content.ContextWrapper;
     21 import android.content.SharedPreferences;
     22 import android.content.pm.PackageManager.NameNotFoundException;
     23 import android.os.Build;
     24 import android.os.Bundle;
     25 import android.test.InstrumentationTestCase;
     26 import android.test.MoreAsserts;
     27 import android.test.suitebuilder.annotation.SmallTest;
     28 
     29 import com.android.email.DBTestHelper;
     30 import com.android.email.MockSharedPreferences;
     31 import com.android.email.MockVendorPolicy;
     32 import com.android.email.VendorPolicyLoader;
     33 import com.android.email.mail.Transport;
     34 import com.android.email.mail.store.ImapStore.ImapMessage;
     35 import com.android.email.mail.store.imap.ImapResponse;
     36 import com.android.email.mail.store.imap.ImapTestUtils;
     37 import com.android.email.mail.transport.MockTransport;
     38 import com.android.emailcommon.TempDirectory;
     39 import com.android.emailcommon.internet.MimeBodyPart;
     40 import com.android.emailcommon.internet.MimeMultipart;
     41 import com.android.emailcommon.internet.MimeUtility;
     42 import com.android.emailcommon.internet.TextBody;
     43 import com.android.emailcommon.mail.Address;
     44 import com.android.emailcommon.mail.AuthenticationFailedException;
     45 import com.android.emailcommon.mail.Body;
     46 import com.android.emailcommon.mail.FetchProfile;
     47 import com.android.emailcommon.mail.Flag;
     48 import com.android.emailcommon.mail.Folder;
     49 import com.android.emailcommon.mail.Folder.FolderType;
     50 import com.android.emailcommon.mail.Folder.OpenMode;
     51 import com.android.emailcommon.mail.Message;
     52 import com.android.emailcommon.mail.Message.RecipientType;
     53 import com.android.emailcommon.mail.MessagingException;
     54 import com.android.emailcommon.mail.Part;
     55 import com.android.emailcommon.provider.Account;
     56 import com.android.emailcommon.provider.HostAuth;
     57 import com.android.emailcommon.provider.Mailbox;
     58 import com.android.emailcommon.utility.Utility;
     59 
     60 import org.apache.commons.io.IOUtils;
     61 
     62 import java.util.ArrayList;
     63 import java.util.HashMap;
     64 import java.util.regex.Pattern;
     65 
     66 /**
     67  * This is a series of unit tests for the ImapStore class.  These tests must be locally
     68  * complete - no server(s) required.
     69  *
     70  * To run these tests alone, use:
     71  *   $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
     72  *
     73  * TODO Check if callback is really called
     74  * TODO test for BAD response in various places?
     75  * TODO test for BYE response in various places?
     76  */
     77 @SmallTest
     78 public class ImapStoreUnitTests extends InstrumentationTestCase {
     79     private final static String[] NO_REPLY = new String[0];
     80 
     81     /** Default folder name.  In order to test for encoding, we use a non-ascii name. */
     82     private final static String FOLDER_NAME = "\u65E5";
     83     /** Folder name encoded in UTF-7. */
     84     private final static String FOLDER_ENCODED = "&ZeU-";
     85     /**
     86      * Flag bits to specify whether or not a folder can be selected. This corresponds to
     87      * {@link Mailbox#FLAG_ACCEPTS_MOVED_MAIL} and {@link Mailbox#FLAG_HOLDS_MAIL}.
     88      */
     89     private final static int SELECTABLE_BITS = 0x18;
     90 
     91     private final static ImapResponse CAPABILITY_RESPONSE = ImapTestUtils.parseResponse(
     92             "* CAPABILITY IMAP4rev1 STARTTLS");
     93 
     94     /* These values are provided by setUp() */
     95     private ImapStore mStore = null;
     96     private ImapFolder mFolder = null;
     97     private Context mTestContext;
     98 
     99     /** The tag for the current IMAP command; used for mock transport responses */
    100     private int mTag;
    101     // Fields specific to the CopyMessages tests
    102     private MockTransport mCopyMock;
    103     private Folder mCopyToFolder;
    104     private Message[] mCopyMessages;
    105 
    106     /**
    107      * A wrapper to provide a wrapper to a Context which has already been mocked.
    108      * This allows additional methods to delegate to the original, real context, in cases
    109      * where the mocked behavior is insufficient.
    110      */
    111     private class SecondaryMockContext extends ContextWrapper {
    112         private final Context mUnderlying;
    113 
    114         public SecondaryMockContext(Context mocked, Context underlying) {
    115             super(mocked);
    116             mUnderlying = underlying;
    117         }
    118 
    119         // TODO: eliminate the need for these method.
    120         @Override
    121         public Context createPackageContext(String packageName, int flags)
    122                 throws NameNotFoundException {
    123             return mUnderlying.createPackageContext(packageName, flags);
    124         }
    125 
    126         @Override
    127         public SharedPreferences getSharedPreferences(String name, int mode) {
    128             return new MockSharedPreferences();
    129         }
    130     }
    131 
    132     /**
    133      * Setup code.  We generate a lightweight ImapStore and ImapStore.ImapFolder.
    134      */
    135     @Override
    136     protected void setUp() throws Exception {
    137         super.setUp();
    138         Context realContext = getInstrumentation().getTargetContext();
    139         ImapStore.sImapId = ImapStore.makeCommonImapId(realContext.getPackageName(),
    140                         Build.VERSION.RELEASE, Build.VERSION.CODENAME,
    141                         Build.MODEL, Build.ID, Build.MANUFACTURER,
    142                         "FakeNetworkOperator");
    143         mTestContext = new SecondaryMockContext(
    144                 DBTestHelper.ProviderContextSetupHelper.getProviderContext(realContext),
    145                 realContext);
    146         MockVendorPolicy.inject(mTestContext);
    147 
    148         TempDirectory.setTempDirectory(mTestContext);
    149 
    150         // These are needed so we can get at the inner classes
    151         HostAuth testAuth = new HostAuth();
    152         Account testAccount = new Account();
    153 
    154         testAuth.setLogin("user", "password");
    155         testAuth.setConnection("imap", "server", 999);
    156         testAccount.mHostAuthRecv = testAuth;
    157         mStore = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
    158         mFolder = (ImapFolder) mStore.getFolder(FOLDER_NAME);
    159         resetTag();
    160     }
    161 
    162     public void testJoinMessageUids() throws Exception {
    163         assertEquals("", ImapStore.joinMessageUids(new Message[] {}));
    164         assertEquals("a", ImapStore.joinMessageUids(new Message[] {
    165                 mFolder.createMessage("a")
    166                 }));
    167         assertEquals("a,XX", ImapStore.joinMessageUids(new Message[] {
    168                 mFolder.createMessage("a"),
    169                 mFolder.createMessage("XX"),
    170                 }));
    171     }
    172 
    173     /**
    174      * Confirms simple non-SSL non-TLS login
    175      */
    176     public void testSimpleLogin() throws MessagingException {
    177 
    178         MockTransport mockTransport = openAndInjectMockTransport();
    179 
    180         // try to open it
    181         setupOpenFolder(mockTransport);
    182         mFolder.open(OpenMode.READ_WRITE);
    183 
    184         // TODO: inject specific facts in the initial folder SELECT and check them here
    185     }
    186 
    187     /**
    188      * Test simple login with failed authentication
    189      */
    190     public void testLoginFailure() throws Exception {
    191         MockTransport mockTransport = openAndInjectMockTransport();
    192         expectLogin(mockTransport, false, false, false, new String[] {"* iD nIL", "oK"},
    193                 "nO authentication failed");
    194 
    195         try {
    196             mStore.getConnection().open();
    197             fail("Didn't throw AuthenticationFailedException");
    198         } catch (AuthenticationFailedException expected) {
    199         }
    200     }
    201 
    202     /**
    203      * Test simple TLS open
    204      */
    205     public void testTlsOpen() throws MessagingException {
    206 
    207         MockTransport mockTransport = openAndInjectMockTransport(Transport.CONNECTION_SECURITY_TLS,
    208                 false);
    209 
    210         // try to open it, with STARTTLS
    211         expectLogin(mockTransport, true, false, false,
    212                 new String[] {"* iD nIL", "oK"}, "oK user authenticated (Success)");
    213         mockTransport.expect(
    214                 getNextTag(false) + " SELECT \"" + FOLDER_ENCODED + "\"", new String[] {
    215                 "* fLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
    216                 "* oK [pERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
    217                 "* 0 eXISTS",
    218                 "* 0 rECENT",
    219                 "* OK [uNSEEN 0]",
    220                 "* OK [uIDNEXT 1]",
    221                 getNextTag(true) + " oK [" + "rEAD-wRITE" + "] " +
    222                         FOLDER_ENCODED + " selected. (Success)"});
    223 
    224         mFolder.open(OpenMode.READ_WRITE);
    225         assertTrue(mockTransport.isTlsStarted());
    226     }
    227 
    228     /**
    229      * TODO: Test with SSL negotiation (faked)
    230      * TODO: Test with SSL required but not supported
    231      * TODO: Test with TLS required but not supported
    232      */
    233 
    234     /**
    235      * Test the generation of the IMAP ID keys
    236      */
    237     public void testImapIdBasic() {
    238         // First test looks at operation of the outer API - we don't control any of the
    239         // values;  Just look for basic results.
    240 
    241         // Strings we'll expect to find:
    242         //   name            Android package name of the program
    243         //   os              "android"
    244         //   os-version      "version; build-id"
    245         //   vendor          Vendor of the client/server
    246         //   x-android-device-model Model (Optional, so not tested here)
    247         //   x-android-net-operator Carrier (Unreliable, so not tested here)
    248         //   AGUID           A device+account UID
    249         String id = ImapStore.getImapId(mTestContext, "user-name", "host-name",
    250                 CAPABILITY_RESPONSE.flatten());
    251         HashMap<String, String> map = tokenizeImapId(id);
    252         assertEquals(mTestContext.getPackageName(), map.get("name"));
    253         assertEquals("android", map.get("os"));
    254         assertNotNull(map.get("os-version"));
    255         assertNotNull(map.get("vendor"));
    256         assertNotNull(map.get("AGUID"));
    257 
    258         // Next, use the inner API to confirm operation of a couple of
    259         // variants for release and non-release devices.
    260 
    261         // simple API check - non-REL codename, non-empty version
    262         id = ImapStore.makeCommonImapId("packageName", "version", "codeName",
    263                 "model", "id", "vendor", "network-operator");
    264         map = tokenizeImapId(id);
    265         assertEquals("packageName", map.get("name"));
    266         assertEquals("android", map.get("os"));
    267         assertEquals("version; id", map.get("os-version"));
    268         assertEquals("vendor", map.get("vendor"));
    269         assertEquals(null, map.get("x-android-device-model"));
    270         assertEquals("network-operator", map.get("x-android-mobile-net-operator"));
    271         assertEquals(null, map.get("AGUID"));
    272 
    273         // simple API check - codename is REL, so use model name.
    274         // also test empty version => 1.0 and empty network operator
    275         id = ImapStore.makeCommonImapId("packageName", "", "REL",
    276                 "model", "id", "vendor", "");
    277         map = tokenizeImapId(id);
    278         assertEquals("packageName", map.get("name"));
    279         assertEquals("android", map.get("os"));
    280         assertEquals("1.0; id", map.get("os-version"));
    281         assertEquals("vendor", map.get("vendor"));
    282         assertEquals("model", map.get("x-android-device-model"));
    283         assertEquals(null, map.get("x-android-mobile-net-operator"));
    284         assertEquals(null, map.get("AGUID"));
    285     }
    286 
    287     /**
    288      * Test for the interaction between {@link ImapStore#getImapId} and a vendor policy.
    289      */
    290     public void testImapIdWithVendorPolicy() {
    291         try {
    292             MockVendorPolicy.inject(mTestContext);
    293 
    294             // Prepare mock result
    295             Bundle result = new Bundle();
    296             result.putString("getImapId", "\"test-key\" \"test-value\"");
    297             MockVendorPolicy.mockResult = result;
    298 
    299             // Invoke
    300             String id = ImapStore.getImapId(mTestContext, "user-name", "host-name",
    301                     ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z").flatten());
    302 
    303             // Check the result
    304             assertEquals("test-value", tokenizeImapId(id).get("test-key"));
    305 
    306             // Verify what's passed to the policy
    307             assertEquals("getImapId", MockVendorPolicy.passedPolicy);
    308             assertEquals("user-name", MockVendorPolicy.passedBundle.getString("getImapId.user"));
    309             assertEquals("host-name", MockVendorPolicy.passedBundle.getString("getImapId.host"));
    310             assertEquals("[CAPABILITY,IMAP4rev1,XXX,YYY,Z]",
    311                     MockVendorPolicy.passedBundle.getString("getImapId.capabilities"));
    312         } finally {
    313             VendorPolicyLoader.clearInstanceForTest();
    314         }
    315     }
    316 
    317     /**
    318      * Test of the internal generator for IMAP ID strings, specifically looking for proper
    319      * filtering of illegal values.  This is required because we cannot necessarily trust
    320      * the external sources of some of this data (e.g. release labels).
    321      *
    322      * The (somewhat arbitrary) legal values are:  a-z A-Z 0-9 - _ + = ; : . , / <space>
    323      * The most important goal of the filters is to keep out control chars, (, ), and "
    324      */
    325     public void testImapIdFiltering() {
    326         String id = ImapStore.makeCommonImapId(
    327                 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    328                 "0123456789", "codeName",
    329                 "model", "-_+=;:.,// ",
    330                 "v(e)n\"d\ro\nr",           // look for bad chars stripped out, leaving OK chars
    331                 "()\"");                    // look for bad chars stripped out, leaving nothing
    332         HashMap<String, String> map = tokenizeImapId(id);
    333 
    334         assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name"));
    335         assertEquals("0123456789; -_+=;:.,// ", map.get("os-version"));
    336         assertEquals("vendor", map.get("vendor"));
    337         assertNull(map.get("x-android-mobile-net-operator"));
    338     }
    339 
    340     /**
    341      * Test that IMAP ID uid's are per-username
    342      */
    343     public void testImapIdDeviceId() throws MessagingException {
    344         HostAuth testAuth;
    345         Account testAccount;
    346 
    347         // store 1a
    348         testAuth = new HostAuth();
    349         testAuth.setLogin("user1", "password");
    350         testAuth.setConnection("imap", "server", 999);
    351         testAccount = new Account();
    352         testAccount.mHostAuthRecv = testAuth;
    353         ImapStore testStore1A = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
    354 
    355         // store 1b
    356         testAuth = new HostAuth();
    357         testAuth.setLogin("user1", "password");
    358         testAuth.setConnection("imap", "server", 999);
    359         testAccount = new Account();
    360         testAccount.mHostAuthRecv = testAuth;
    361         ImapStore testStore1B = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
    362 
    363         // store 2
    364         testAuth = new HostAuth();
    365         testAuth.setLogin("user2", "password");
    366         testAuth.setConnection("imap", "server", 999);
    367         testAccount = new Account();
    368         testAccount.mHostAuthRecv = testAuth;
    369         ImapStore testStore2 = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
    370 
    371         String capabilities = CAPABILITY_RESPONSE.flatten();
    372         String id1a = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities);
    373         String id1b = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities);
    374         String id2 = ImapStore.getImapId(mTestContext, "user2", "host-name", capabilities);
    375 
    376         String uid1a = tokenizeImapId(id1a).get("AGUID");
    377         String uid1b = tokenizeImapId(id1b).get("AGUID");
    378         String uid2 = tokenizeImapId(id2).get("AGUID");
    379 
    380         assertEquals(uid1a, uid1b);
    381         MoreAsserts.assertNotEqual(uid1a, uid2);
    382     }
    383 
    384     /**
    385      * Helper to break an IMAP ID string into keys & values
    386      * @param id the IMAP Id string (the part inside the parens)
    387      * @return a map of key/value pairs
    388      */
    389     private HashMap<String, String> tokenizeImapId(String id) {
    390         // Instead of a true tokenizer, we'll use double-quote as the split.
    391         // We can's use " " because there may be spaces inside the values.
    392         String[] elements = id.split("\"");
    393         HashMap<String, String> map = new HashMap<String, String>();
    394         for (int i = 0; i < elements.length; ) {
    395             // Because we split at quotes, we expect to find:
    396             // [i] = null or one or more spaces
    397             // [i+1] = key
    398             // [i+2] = one or more spaces
    399             // [i+3] = value
    400             map.put(elements[i+1], elements[i+3]);
    401             i += 4;
    402         }
    403         return map;
    404     }
    405 
    406     /**
    407      * Test non-NIL server response to IMAP ID.  We should simply ignore it.
    408      */
    409     public void testServerId() throws MessagingException {
    410         MockTransport mockTransport = openAndInjectMockTransport();
    411 
    412         // try to open it
    413         setupOpenFolder(mockTransport, new String[] {
    414                 "* ID (\"name\" \"Cyrus\" \"version\" \"1.5\"" +
    415                 " \"os\" \"sunos\" \"os-version\" \"5.5\"" +
    416                 " \"support-url\" \"mailto:cyrus-bugs+@andrew.cmu.edu\")",
    417                 "oK"}, "rEAD-wRITE");
    418         mFolder.open(OpenMode.READ_WRITE);
    419     }
    420 
    421     /**
    422      * Test OK response to IMAP ID with crummy text afterwards too.
    423      */
    424     public void testImapIdOkParsing() throws MessagingException {
    425         MockTransport mockTransport = openAndInjectMockTransport();
    426 
    427         // try to open it
    428         setupOpenFolder(mockTransport, new String[] {
    429                 "* iD nIL",
    430                 "oK [iD] bad-char-%"}, "rEAD-wRITE");
    431         mFolder.open(OpenMode.READ_WRITE);
    432     }
    433 
    434     /**
    435      * Test BAD response to IMAP ID - also with bad parser chars
    436      */
    437     public void testImapIdBad() throws MessagingException {
    438         MockTransport mockTransport = openAndInjectMockTransport();
    439 
    440         // try to open it
    441         setupOpenFolder(mockTransport, new String[] {
    442                 "bAD unknown command bad-char-%"}, "rEAD-wRITE");
    443         mFolder.open(OpenMode.READ_WRITE);
    444     }
    445 
    446     /**
    447      * Confirm that when IMAP ID is not in capability, it is not sent/received.
    448      * This supports RFC 2971 section 3, and is important because certain servers
    449      * (e.g. imap.vodafone.net.nz) do not process the unexpected ID command properly.
    450      */
    451     public void testImapIdNotSupported() throws MessagingException {
    452         MockTransport mockTransport = openAndInjectMockTransport();
    453 
    454         // try to open it
    455         setupOpenFolder(mockTransport, null, "rEAD-wRITE");
    456         mFolder.open(OpenMode.READ_WRITE);
    457     }
    458 
    459     /**
    460      * Confirm that the non-conformant IMAP ID result seen on imap.secureserver.net fails
    461      * to properly parse.
    462      *   2 ID ("name" "com.google.android.email")
    463      *   * ID( "name" "Godaddy IMAP" ... "version" "3.1.0")
    464      *   2 OK ID completed
    465      */
    466     public void testImapIdSecureServerParseFail() {
    467         MockTransport mockTransport = openAndInjectMockTransport();
    468 
    469         // configure mock server to return malformed ID response
    470         setupOpenFolder(mockTransport, new String[] {
    471                 "* ID( \"name\" \"Godaddy IMAP\" \"version\" \"3.1.0\")",
    472                 "oK"}, "rEAD-wRITE");
    473         try {
    474             mFolder.open(OpenMode.READ_WRITE);
    475             fail("Expected MessagingException");
    476         } catch (MessagingException expected) {
    477         }
    478     }
    479 
    480     /**
    481      * Confirm that the connections to *.secureserver.net never send IMAP ID (see
    482      * testImapIdSecureServerParseFail() for the reason why.)
    483      */
    484     public void testImapIdSecureServerNotSent() throws MessagingException {
    485         // Note, this is injected into mStore (which we don't use for this test)
    486         MockTransport mockTransport = openAndInjectMockTransport();
    487         mockTransport.setHost("eMail.sEcurEserVer.nEt");
    488 
    489         // Prime the expects pump as if the server wants IMAP ID, but we should not actually expect
    490         // to send it, because the login code in the store should never actually send it (to this
    491         // particular server).  This sequence is a minimized version of expectLogin().
    492 
    493         // Respond to the initial connection
    494         mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You");
    495         // Return "ID" in the capability
    496         expectCapability(mockTransport, true, false);
    497         // No TLS
    498         // No ID (the special case for this server)
    499         // LOGIN
    500         mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"",
    501                 getNextTag(true) + " " + "oK user authenticated (Success)");
    502         // SELECT
    503         expectSelect(mockTransport, FOLDER_ENCODED, "rEAD-wRITE");
    504 
    505         // Now open the folder.  Although the server indicates ID in the capabilities,
    506         // we are not expecting the store to send the ID command (to this particular server).
    507         mFolder.open(OpenMode.READ_WRITE);
    508     }
    509 
    510     /**
    511      * Test small Folder functions that don't really do anything in Imap
    512      */
    513     public void testSmallFolderFunctions() {
    514         // canCreate() returns true
    515         assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
    516         assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
    517     }
    518 
    519     /**
    520      * Lightweight test to confirm that IMAP hasn't implemented any folder roles yet.
    521      *
    522      * TODO: Test this with multiple folders provided by mock server
    523      * TODO: Implement XLIST and then support this
    524      */
    525     public void testNoFolderRolesYet() {
    526         assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole());
    527     }
    528 
    529     /**
    530      * Lightweight test to confirm that IMAP is requesting sent-message-upload.
    531      * TODO: Implement Gmail-specific cases and handle this server-side
    532      */
    533     public void testSentUploadRequested() {
    534         assertTrue(mStore.requireCopyMessageToSentFolder());
    535     }
    536 
    537     /**
    538      * TODO: Test the process of opening and indexing a mailbox with one unread message in it.
    539      */
    540 
    541     /**
    542      * TODO: Test the scenario where the transport is "open" but not really (e.g. server closed).
    543     /**
    544      * Set up a basic MockTransport. open it, and inject it into mStore
    545      */
    546     private MockTransport openAndInjectMockTransport() {
    547         return openAndInjectMockTransport(Transport.CONNECTION_SECURITY_NONE, false);
    548     }
    549 
    550     /**
    551      * Set up a MockTransport with security settings
    552      */
    553     private MockTransport openAndInjectMockTransport(int connectionSecurity,
    554             boolean trustAllCertificates) {
    555         // Create mock transport and inject it into the ImapStore that's already set up
    556         MockTransport mockTransport = new MockTransport();
    557         mockTransport.setSecurity(connectionSecurity, trustAllCertificates);
    558         mockTransport.setHost("mock.server.com");
    559         mStore.setTransportForTest(mockTransport);
    560         return mockTransport;
    561     }
    562 
    563     /**
    564      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    565      *
    566      * @param mockTransport the mock transport we're using
    567      */
    568     private void setupOpenFolder(MockTransport mockTransport) {
    569         setupOpenFolder(mockTransport, "rEAD-wRITE");
    570     }
    571 
    572     /**
    573      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    574      *
    575      * @param mockTransport the mock transport we're using
    576      */
    577     private void setupOpenFolder(MockTransport mockTransport, String readWriteMode) {
    578         setupOpenFolder(mockTransport, new String[] {
    579                 "* iD nIL", "oK"}, readWriteMode, false);
    580     }
    581 
    582     /**
    583      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    584      * Also allows setting a custom IMAP ID.
    585      *
    586      * Also sets mNextTag, an int, which is useful if there are additional commands to inject.
    587      *
    588      * @param mockTransport the mock transport we're using
    589      * @param imapIdResponse the expected series of responses to the IMAP ID command.  Non-final
    590      *      lines should be tagged with *.  The final response should be untagged (the correct
    591      *      tag will be added at runtime).  Pass "null" to test w/o IMAP ID.
    592      * @param readWriteMode "READ-WRITE" or "READ-ONLY"
    593      */
    594     private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
    595             String readWriteMode) {
    596         setupOpenFolder(mockTransport, imapIdResponse, readWriteMode, false);
    597     }
    598 
    599     private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
    600             String readWriteMode, boolean withUidPlus) {
    601         expectLogin(mockTransport, imapIdResponse, withUidPlus);
    602         expectSelect(mockTransport, FOLDER_ENCODED, readWriteMode);
    603     }
    604 
    605     /**
    606      * Helper which stuffs the mock with the strings to satisfy a typical SELECT.
    607      * @param mockTransport the mock transport we're using
    608      * @param readWriteMode "READ-WRITE" or "READ-ONLY"
    609      */
    610     private void expectSelect(MockTransport mockTransport, String folder, String readWriteMode) {
    611         mockTransport.expect(
    612                 getNextTag(false) + " SELECT \"" + folder + "\"", new String[] {
    613                 "* fLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
    614                 "* oK [pERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
    615                 "* 0 eXISTS",
    616                 "* 0 rECENT",
    617                 "* OK [uNSEEN 0]",
    618                 "* OK [uIDNEXT 1]",
    619                 getNextTag(true) + " oK [" + readWriteMode + "] " +
    620                         folder + " selected. (Success)"});
    621     }
    622 
    623     private void expectLogin(MockTransport mockTransport) {
    624         expectLogin(mockTransport, new String[] {"* iD nIL", "oK"}, false);
    625     }
    626 
    627     private void expectLogin(MockTransport mockTransport, String[] imapIdResponse,
    628             boolean withUidPlus) {
    629         expectLogin(mockTransport, false, (imapIdResponse != null), withUidPlus, imapIdResponse,
    630                 "oK user authenticated (Success)");
    631     }
    632 
    633     private void expectLogin(MockTransport mockTransport, boolean startTls, boolean withId,
    634             boolean withUidPlus, String[] imapIdResponse, String loginResponse) {
    635         // inject boilerplate commands that match our typical login
    636         mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You");
    637 
    638         expectCapability(mockTransport, withId, withUidPlus);
    639 
    640         // TLS (if expected)
    641         if (startTls) {
    642             mockTransport.expect(getNextTag(false) + " STARTTLS",
    643                 getNextTag(true) + " Ok starting TLS");
    644             mockTransport.expectStartTls();
    645             // After switching to TLS the client must re-query for capability
    646             expectCapability(mockTransport, withId, withUidPlus);
    647         }
    648 
    649         // ID
    650         if (withId) {
    651             String expectedNextTag = getNextTag(false);
    652             // Fix the tag # of the ID response
    653             String last = imapIdResponse[imapIdResponse.length-1];
    654             last = expectedNextTag + " " + last;
    655             imapIdResponse[imapIdResponse.length-1] = last;
    656             mockTransport.expect(getNextTag(false) + " ID \\(.*\\)", imapIdResponse);
    657             getNextTag(true); // Advance the tag for ID response.
    658         }
    659 
    660         // LOGIN
    661         mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"",
    662                 getNextTag(true) + " " + loginResponse);
    663     }
    664 
    665     private void expectCapability(MockTransport mockTransport, boolean withId,
    666             boolean withUidPlus) {
    667         String capabilityList = "* cAPABILITY iMAP4rev1 sTARTTLS aUTH=gSSAPI lOGINDISABLED";
    668         capabilityList += withId ? " iD" : "";
    669         capabilityList += withUidPlus ? " UiDPlUs" : "";
    670 
    671         mockTransport.expect(getNextTag(false) + " CAPABILITY", new String[] {
    672             capabilityList,
    673             getNextTag(true) + " oK CAPABILITY completed"});
    674     }
    675 
    676     private void expectNoop(MockTransport mockTransport, boolean ok) {
    677         String response = ok ? " oK success" : " nO timeout";
    678         mockTransport.expect(getNextTag(false) + " NOOP",
    679                 new String[] {getNextTag(true) + response});
    680     }
    681 
    682     /**
    683      * Return a tag for use in setting up expect strings.  Typically this is called in pairs,
    684      * first as getNextTag(false) when emitting the command, then as getNextTag(true) when
    685      * emitting the final line of the expected response.
    686      * @param advance true to increment mNextTag for the subsequence command
    687      * @return a string containing the current tag
    688      */
    689     public String getNextTag(boolean advance)  {
    690         if (advance) ++mTag;
    691         return Integer.toString(mTag);
    692     }
    693 
    694     /**
    695      * Resets the tag back to it's starting value. Do this after the test connection has been
    696      * closed.
    697      */
    698     private int resetTag() {
    699         return resetTag(1);
    700     }
    701 
    702     private int resetTag(int tag) {
    703         int oldTag = mTag;
    704         mTag = tag;
    705         return oldTag;
    706     }
    707 
    708     /**
    709      * Test that servers reporting READ-WRITE mode are parsed properly
    710      * Note: the READ_WRITE mode passed to folder.open() does not affect the test
    711      */
    712     public void testReadWrite() throws MessagingException {
    713         MockTransport mock = openAndInjectMockTransport();
    714         setupOpenFolder(mock, "rEAD-WRITE");
    715         mFolder.open(OpenMode.READ_WRITE);
    716         assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
    717     }
    718 
    719     /**
    720      * Test that servers reporting READ-ONLY mode are parsed properly
    721      * Note: the READ_ONLY mode passed to folder.open() does not affect the test
    722      */
    723     public void testReadOnly() throws MessagingException {
    724         MockTransport mock = openAndInjectMockTransport();
    725         setupOpenFolder(mock, "rEAD-ONLY");
    726         mFolder.open(OpenMode.READ_ONLY);
    727         assertEquals(OpenMode.READ_ONLY, mFolder.getMode());
    728     }
    729 
    730     /**
    731      * Test for getUnreadMessageCount with quoted string in the middle of response.
    732      */
    733     public void testGetUnreadMessageCountWithQuotedString() throws Exception {
    734         MockTransport mock = openAndInjectMockTransport();
    735         setupOpenFolder(mock);
    736         mock.expect(
    737                 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
    738                 new String[] {
    739                 "* sTATUS \"" + FOLDER_ENCODED + "\" (uNSEEN 2)",
    740                 getNextTag(true) + " oK STATUS completed"});
    741         mFolder.open(OpenMode.READ_WRITE);
    742         int unreadCount = mFolder.getUnreadMessageCount();
    743         assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount);
    744     }
    745 
    746     /**
    747      * Test for getUnreadMessageCount with literal string in the middle of response.
    748      */
    749     public void testGetUnreadMessageCountWithLiteralString() throws Exception {
    750         MockTransport mock = openAndInjectMockTransport();
    751         setupOpenFolder(mock);
    752         mock.expect(
    753                 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
    754                 new String[] {
    755                 "* sTATUS {5}",
    756                 FOLDER_ENCODED + " (uNSEEN 10)",
    757                 getNextTag(true) + " oK STATUS completed"});
    758         mFolder.open(OpenMode.READ_WRITE);
    759         int unreadCount = mFolder.getUnreadMessageCount();
    760         assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
    761     }
    762 
    763     public void testFetchFlagEnvelope() throws MessagingException {
    764         final MockTransport mock = openAndInjectMockTransport();
    765         setupOpenFolder(mock);
    766         mFolder.open(OpenMode.READ_WRITE);
    767         final Message message = mFolder.createMessage("1");
    768 
    769         final FetchProfile fp = new FetchProfile();
    770         fp.add(FetchProfile.Item.FLAGS);
    771         fp.add(FetchProfile.Item.ENVELOPE);
    772         mock.expect(getNextTag(false) +
    773                 " UID FETCH 1 \\(UID FLAGS INTERNALDATE RFC822\\.SIZE BODY\\.PEEK\\[HEADER.FIELDS" +
    774                         " \\(date subject from content-type to cc message-id\\)\\]\\)",
    775                 new String[] {
    776                 "* 9 fETCH (uID 1 rFC822.sIZE 120626 iNTERNALDATE \"17-may-2010 22:00:15 +0000\"" +
    777                         "fLAGS (\\Seen) bODY[hEADER.FIELDS (dAte sUbject fRom cOntent-type tO cC" +
    778                         " mEssage-id)]" +
    779                         " {279}",
    780                 "From: Xxxxxx Yyyyy <userxx (at) android.com>",
    781                 "Date: Mon, 17 May 2010 14:59:52 -0700",
    782                 "Message-ID: <x0000000000000000000000000000000000000000000000y (at) android.com>",
    783                 "Subject: ssubject",
    784                 "To: android.test01 (at) android.com",
    785                 "Content-Type: multipart/mixed; boundary=a00000000000000000000000000b",
    786                 "",
    787                 ")",
    788                 getNextTag(true) + " oK SUCCESS"
    789         });
    790         mFolder.fetch(new Message[] { message }, fp, null);
    791 
    792         assertEquals("android.test01 (at) android.com", message.getHeader("to")[0]);
    793         assertEquals("Xxxxxx Yyyyy <userxx (at) android.com>", message.getHeader("from")[0]);
    794         assertEquals("multipart/mixed; boundary=a00000000000000000000000000b",
    795                 message.getHeader("Content-Type")[0]);
    796         assertTrue(message.isSet(Flag.SEEN));
    797 
    798         // TODO: Test NO response.
    799     }
    800 
    801     /**
    802      * Test for fetching simple BODYSTRUCTURE.
    803      */
    804     public void testFetchBodyStructureSimple() throws Exception {
    805         final MockTransport mock = openAndInjectMockTransport();
    806         setupOpenFolder(mock);
    807         mFolder.open(OpenMode.READ_WRITE);
    808         final Message message = mFolder.createMessage("1");
    809 
    810         final FetchProfile fp = new FetchProfile();
    811         fp.add(FetchProfile.Item.STRUCTURE);
    812         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
    813                 new String[] {
    814                 "* 9 fETCH (uID 1 bODYSTRUCTURE (\"tEXT\" \"pLAIN\" nIL" +
    815                         " nIL nIL nIL 18 3 nIL nIL nIL))",
    816                 getNextTag(true) + " oK sUCCESS"
    817         });
    818         mFolder.fetch(new Message[] { message }, fp, null);
    819 
    820         // Check mime structure...
    821         MoreAsserts.assertEquals(
    822                 new String[] {"text/plain"},
    823                 message.getHeader("Content-Type")
    824                 );
    825         assertNull(message.getHeader("Content-Transfer-Encoding"));
    826         assertNull(message.getHeader("Content-ID"));
    827         MoreAsserts.assertEquals(
    828                 new String[] {";\n size=18"},
    829                 message.getHeader("Content-Disposition")
    830                 );
    831 
    832         MoreAsserts.assertEquals(
    833                 new String[] {"TEXT"},
    834                 message.getHeader("X-Android-Attachment-StoreData")
    835                 );
    836 
    837         // TODO: Test NO response.
    838     }
    839 
    840     /**
    841      * Test for fetching complex muiltipart BODYSTRUCTURE.
    842      */
    843     public void testFetchBodyStructureMultipart() throws Exception {
    844         final MockTransport mock = openAndInjectMockTransport();
    845         setupOpenFolder(mock);
    846         mFolder.open(OpenMode.READ_WRITE);
    847         final Message message = mFolder.createMessage("1");
    848 
    849         final FetchProfile fp = new FetchProfile();
    850         fp.add(FetchProfile.Item.STRUCTURE);
    851         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
    852                 new String[] {
    853                 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"pLAIN\" () {20}",
    854                 "long content id#@!@#" +
    855                     " NIL \"7BIT\" 18 3 NIL NIL NIL)" +
    856                     "(\"IMAGE\" \"PNG\" (\"NAME\" {10}",
    857                 "device.png) NIL NIL \"BASE64\" {6}",
    858                 "117840 NIL (\"aTTACHMENT\" (\"fILENAME\" \"device.png\")) NIL)" +
    859                     "(\"TEXT\" \"HTML\" () NIL NIL \"7BIT\" 100 NIL 123 (\"aTTACHMENT\"" +
    860                     "(\"fILENAME\" {15}",
    861                 "attachment.html \"SIZE\" 555)) NIL)" +
    862                     "((\"TEXT\" \"HTML\" NIL NIL \"BASE64\")(\"XXX\" \"YYY\"))" + // Nested
    863                     "\"mIXED\" (\"bOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))",
    864                 getNextTag(true) + " oK SUCCESS"
    865         });
    866         mFolder.fetch(new Message[] { message }, fp, null);
    867 
    868         // Check mime structure...
    869         final Body body = message.getBody();
    870         assertTrue(body instanceof MimeMultipart);
    871         MimeMultipart mimeMultipart = (MimeMultipart) body;
    872         assertEquals(4, mimeMultipart.getCount());
    873         assertEquals("mixed", mimeMultipart.getSubTypeForTest());
    874 
    875         final Part part1 = mimeMultipart.getBodyPart(0);
    876         final Part part2 = mimeMultipart.getBodyPart(1);
    877         final Part part3 = mimeMultipart.getBodyPart(2);
    878         final Part part4 = mimeMultipart.getBodyPart(3);
    879         assertTrue(part1 instanceof MimeBodyPart);
    880         assertTrue(part2 instanceof MimeBodyPart);
    881         assertTrue(part3 instanceof MimeBodyPart);
    882         assertTrue(part4 instanceof MimeBodyPart);
    883 
    884         final MimeBodyPart mimePart1 = (MimeBodyPart) part1; // text/plain
    885         final MimeBodyPart mimePart2 = (MimeBodyPart) part2; // image/png
    886         final MimeBodyPart mimePart3 = (MimeBodyPart) part3; // text/html
    887         final MimeBodyPart mimePart4 = (MimeBodyPart) part4; // Nested
    888 
    889         MoreAsserts.assertEquals(
    890                 new String[] {"1"},
    891                 part1.getHeader("X-Android-Attachment-StoreData")
    892                 );
    893         MoreAsserts.assertEquals(
    894                 new String[] {"2"},
    895                 part2.getHeader("X-Android-Attachment-StoreData")
    896                 );
    897         MoreAsserts.assertEquals(
    898                 new String[] {"3"},
    899                 part3.getHeader("X-Android-Attachment-StoreData")
    900                 );
    901 
    902         MoreAsserts.assertEquals(
    903                 new String[] {"text/plain"},
    904                 part1.getHeader("Content-Type")
    905                 );
    906         MoreAsserts.assertEquals(
    907                 new String[] {"image/png;\n NAME=\"device.png\""},
    908                 part2.getHeader("Content-Type")
    909                 );
    910         MoreAsserts.assertEquals(
    911                 new String[] {"text/html"},
    912                 part3.getHeader("Content-Type")
    913                 );
    914 
    915         MoreAsserts.assertEquals(
    916                 new String[] {"long content id#@!@#"},
    917                 part1.getHeader("Content-ID")
    918                 );
    919         assertNull(part2.getHeader("Content-ID"));
    920         assertNull(part3.getHeader("Content-ID"));
    921 
    922         MoreAsserts.assertEquals(
    923                 new String[] {"7BIT"},
    924                 part1.getHeader("Content-Transfer-Encoding")
    925                 );
    926         MoreAsserts.assertEquals(
    927                 new String[] {"BASE64"},
    928                 part2.getHeader("Content-Transfer-Encoding")
    929                 );
    930         MoreAsserts.assertEquals(
    931                 new String[] {"7BIT"},
    932                 part3.getHeader("Content-Transfer-Encoding")
    933                 );
    934 
    935         MoreAsserts.assertEquals(
    936                 new String[] {";\n size=18"},
    937                 part1.getHeader("Content-Disposition")
    938                 );
    939         MoreAsserts.assertEquals(
    940                 new String[] {"attachment;\n filename=\"device.png\";\n size=117840"},
    941                 part2.getHeader("Content-Disposition")
    942                 );
    943         MoreAsserts.assertEquals(
    944                 new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""},
    945                 part3.getHeader("Content-Disposition")
    946                 );
    947 
    948         // Check the nested parts.
    949         final Body part4body = part4.getBody();
    950         assertTrue(part4body instanceof MimeMultipart);
    951         MimeMultipart mimeMultipartPart4 = (MimeMultipart) part4body;
    952         assertEquals(2, mimeMultipartPart4.getCount());
    953 
    954         final MimeBodyPart mimePart41 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(0);
    955         final MimeBodyPart mimePart42 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(1);
    956 
    957         MoreAsserts.assertEquals(new String[] {"4.1"},
    958                 mimePart41.getHeader("X-Android-Attachment-StoreData")
    959                 );
    960         MoreAsserts.assertEquals(new String[] {"4.2"},
    961                 mimePart42.getHeader("X-Android-Attachment-StoreData")
    962                 );
    963     }
    964 
    965     public void testFetchBodySane() throws MessagingException {
    966         final MockTransport mock = openAndInjectMockTransport();
    967         setupOpenFolder(mock);
    968         mFolder.open(OpenMode.READ_WRITE);
    969         final Message message = mFolder.createMessage("1");
    970 
    971         final FetchProfile fp = new FetchProfile();
    972         fp.add(FetchProfile.Item.BODY_SANE);
    973         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]<0.51200>\\)",
    974                 new String[] {
    975                 "* 9 fETCH (uID 1 bODY[] {23}",
    976                 "from: a (at) b.com", // 15 bytes
    977                 "", // 2
    978                 "test", // 6
    979                 ")",
    980                 getNextTag(true) + " oK SUCCESS"
    981         });
    982         mFolder.fetch(new Message[] { message }, fp, null);
    983         assertEquals("a (at) b.com", message.getHeader("from")[0]);
    984 
    985         // TODO: Test NO response.
    986     }
    987 
    988     public void testFetchBody() throws MessagingException {
    989         final MockTransport mock = openAndInjectMockTransport();
    990         setupOpenFolder(mock);
    991         mFolder.open(OpenMode.READ_WRITE);
    992         final Message message = mFolder.createMessage("1");
    993 
    994         final FetchProfile fp = new FetchProfile();
    995         fp.add(FetchProfile.Item.BODY);
    996         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]\\)",
    997                 new String[] {
    998                 "* 9 fETCH (uID 1 bODY[] {23}",
    999                 "from: a (at) b.com", // 15 bytes
   1000                 "", // 2
   1001                 "test", // 6
   1002                 ")",
   1003                 getNextTag(true) + " oK SUCCESS"
   1004         });
   1005         mFolder.fetch(new Message[] { message }, fp, null);
   1006         assertEquals("a (at) b.com", message.getHeader("from")[0]);
   1007 
   1008         // TODO: Test NO response.
   1009     }
   1010 
   1011     public void testFetchAttachment() throws Exception {
   1012         MockTransport mock = openAndInjectMockTransport();
   1013         setupOpenFolder(mock);
   1014         mFolder.open(OpenMode.READ_WRITE);
   1015         final Message message = mFolder.createMessage("1");
   1016 
   1017         final FetchProfile fp = new FetchProfile();
   1018         fp.add(FetchProfile.Item.STRUCTURE);
   1019         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
   1020                 new String[] {
   1021                 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"PLAIN\" (\"cHARSET\" \"iSO-8859-1\")" +
   1022                         " CID nIL \"7bIT\" 18 3 NIL NIL NIL)" +
   1023                         "(\"IMAGE\" \"PNG\"" +
   1024                         " (\"nAME\" \"device.png\") NIL NIL \"bASE64\" 117840 NIL (\"aTTACHMENT\"" +
   1025                         "(\"fILENAME\" \"device.png\")) NIL)" +
   1026                         "\"mIXED\"))",
   1027                 getNextTag(true) + " OK SUCCESS"
   1028         });
   1029         mFolder.fetch(new Message[] { message }, fp, null);
   1030 
   1031         // Check mime structure, and get the second part.
   1032         Body body = message.getBody();
   1033         assertTrue(body instanceof MimeMultipart);
   1034         MimeMultipart mimeMultipart = (MimeMultipart) body;
   1035         assertEquals(2, mimeMultipart.getCount());
   1036 
   1037         Part part1 = mimeMultipart.getBodyPart(1);
   1038         assertTrue(part1 instanceof MimeBodyPart);
   1039         MimeBodyPart mimePart1 = (MimeBodyPart) part1;
   1040 
   1041         // Fetch the second part
   1042         fp.clear();
   1043         fp.add(mimePart1);
   1044         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[2\\]\\)",
   1045                 new String[] {
   1046                 "* 9 fETCH (uID 1 bODY[2] {4}",
   1047                 "YWJj)", // abc in base64
   1048                 getNextTag(true) + " oK SUCCESS"
   1049         });
   1050         mFolder.fetch(new Message[] { message }, fp, null);
   1051 
   1052         assertEquals("abc",
   1053                 Utility.fromUtf8(IOUtils.toByteArray(mimePart1.getBody().getInputStream())));
   1054 
   1055         // TODO: Test NO response.
   1056     }
   1057 
   1058     /**
   1059      * Test for proper operations on servers that return "NIL" for empty message bodies.
   1060      */
   1061     public void testNilMessage() throws MessagingException {
   1062         MockTransport mock = openAndInjectMockTransport();
   1063         setupOpenFolder(mock);
   1064         mFolder.open(OpenMode.READ_WRITE);
   1065 
   1066         // Prepare to pull structure and peek body text - this is like the "large message"
   1067         // loop in MessagingController.synchronizeMailboxGeneric()
   1068         FetchProfile fp = new FetchProfile();fp.clear();
   1069         fp.add(FetchProfile.Item.STRUCTURE);
   1070         Message message1 = mFolder.createMessage("1");
   1071         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
   1072                 "* 1 fETCH (uID 1 bODYSTRUCTURE (tEXT pLAIN nIL nIL nIL 7bIT 0 0 nIL nIL nIL))",
   1073                 getNextTag(true) + " oK SUCCESS"
   1074         });
   1075         mFolder.fetch(new Message[] { message1 }, fp, null);
   1076 
   1077         // The expected result for an empty body is:
   1078         //   * 1 FETCH (UID 1 BODY[TEXT] {0})
   1079         // But some servers are returning NIL for the empty body:
   1080         //   * 1 FETCH (UID 1 BODY[TEXT] NIL)
   1081         // Because this breaks our little parser, fetch() skips over empty parts.
   1082         // The rest of this test is confirming that this is the case.
   1083 
   1084         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
   1085                 "* 1 fETCH (uID 1 bODY[tEXT] nIL)",
   1086                 getNextTag(true) + " oK SUCCESS"
   1087         });
   1088         ArrayList<Part> viewables = new ArrayList<Part>();
   1089         ArrayList<Part> attachments = new ArrayList<Part>();
   1090         MimeUtility.collectParts(message1, viewables, attachments);
   1091         assertTrue(viewables.size() == 1);
   1092         Part emptyBodyPart = viewables.get(0);
   1093         fp.clear();
   1094         fp.add(emptyBodyPart);
   1095         mFolder.fetch(new Message[] { message1 }, fp, null);
   1096 
   1097         // If this wasn't working properly, there would be an attempted interpretation
   1098         // of the empty part's NIL and possibly a crash.
   1099 
   1100         // If this worked properly, the "empty" body can now be retrieved
   1101         viewables = new ArrayList<Part>();
   1102         attachments = new ArrayList<Part>();
   1103         MimeUtility.collectParts(message1, viewables, attachments);
   1104         assertTrue(viewables.size() == 1);
   1105         emptyBodyPart = viewables.get(0);
   1106         String text = MimeUtility.getTextFromPart(emptyBodyPart);
   1107         assertNull(text);
   1108     }
   1109 
   1110     /**
   1111      * Confirm the IMAP parser won't crash when seeing an excess FETCH response line without UID.
   1112      *
   1113      * <p>We've observed that the secure.emailsrvr.com email server returns an excess FETCH response
   1114      * for a UID FETCH command.  These excess responses doesn't have the UID field in it, even
   1115      * though we request, which led the response parser to crash.  We fixed it by ignoring response
   1116      * lines that don't have UID.  This test is to make sure this case.
   1117      */
   1118     public void testExcessFetchResult() throws MessagingException {
   1119         MockTransport mock = openAndInjectMockTransport();
   1120         setupOpenFolder(mock);
   1121         mFolder.open(OpenMode.READ_WRITE);
   1122 
   1123         // Create a message, and make sure it's not "SEEN".
   1124         Message message1 = mFolder.createMessage("1");
   1125         assertFalse(message1.isSet(Flag.SEEN));
   1126 
   1127         FetchProfile fp = new FetchProfile();
   1128         fp.clear();
   1129         fp.add(FetchProfile.Item.FLAGS);
   1130         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID FLAGS\\)",
   1131                 new String[] {
   1132                 "* 1 fETCH (uID 1 fLAGS (\\Seen))",
   1133                 "* 2 fETCH (fLAGS (\\Seen))",
   1134                 getNextTag(true) + " oK SUCCESS"
   1135         });
   1136 
   1137         // Shouldn't crash
   1138         mFolder.fetch(new Message[] { message1 }, fp, null);
   1139 
   1140         // And the message is "SEEN".
   1141         assertTrue(message1.isSet(Flag.SEEN));
   1142     }
   1143 
   1144 
   1145     private ImapMessage prepareForAppendTest(MockTransport mock, String response) throws Exception {
   1146         ImapMessage message = (ImapMessage) mFolder.createMessage("initial uid");
   1147         message.setFrom(new Address("me (at) test.com"));
   1148         message.setRecipient(RecipientType.TO, new Address("you (at) test.com"));
   1149         message.setMessageId("<message.id (at) test.com>");
   1150         message.setFlagDirectlyForTest(Flag.SEEN, true);
   1151         message.setBody(new TextBody("Test Body"));
   1152 
   1153         // + go ahead
   1154         // * 12345 EXISTS
   1155         // OK [APPENDUID 627684530 17] (Success)
   1156 
   1157         mock.expect(getNextTag(false) +
   1158                 " APPEND \\\"" + FOLDER_ENCODED + "\\\" \\(\\\\SEEN\\) \\{166\\}",
   1159                 new String[] {"+ gO aHead"});
   1160 
   1161         mock.expectLiterally("From: me (at) test.com", NO_REPLY);
   1162         mock.expectLiterally("To: you (at) test.com", NO_REPLY);
   1163         mock.expectLiterally("Message-ID: <message.id (at) test.com>", NO_REPLY);
   1164         mock.expectLiterally("Content-Type: text/plain;", NO_REPLY);
   1165         mock.expectLiterally(" charset=utf-8", NO_REPLY);
   1166         mock.expectLiterally("Content-Transfer-Encoding: base64", NO_REPLY);
   1167         mock.expectLiterally("", NO_REPLY);
   1168         mock.expectLiterally("VGVzdCBCb2R5", NO_REPLY);
   1169         mock.expectLiterally("", new String[] {
   1170                 "* 7 eXISTS",
   1171                 getNextTag(true) + " " + response
   1172                 });
   1173         return message;
   1174     }
   1175 
   1176     /**
   1177      * Test for APPEND when the response has APPENDUID.
   1178      */
   1179     public void testAppendMessages() throws Exception {
   1180         MockTransport mock = openAndInjectMockTransport();
   1181         setupOpenFolder(mock);
   1182         mFolder.open(OpenMode.READ_WRITE);
   1183 
   1184         ImapMessage message = prepareForAppendTest(mock, "oK [aPPENDUID 1234567 13] (Success)");
   1185 
   1186         mFolder.appendMessages(new Message[] {message});
   1187 
   1188         assertEquals("13", message.getUid());
   1189         assertEquals(7, mFolder.getMessageCount());
   1190     }
   1191 
   1192     /**
   1193      * Test for APPEND when the response doesn't have APPENDUID.
   1194      */
   1195     public void testAppendMessagesNoAppendUid() throws Exception {
   1196         MockTransport mock = openAndInjectMockTransport();
   1197         setupOpenFolder(mock);
   1198         mFolder.open(OpenMode.READ_WRITE);
   1199 
   1200         ImapMessage message = prepareForAppendTest(mock, "OK Success");
   1201 
   1202         // First try w/o parenthesis
   1203         mock.expectLiterally(
   1204                 getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id (at) test.com>",
   1205                 new String[] {
   1206                     "* sEARCH 321",
   1207                     getNextTag(true) + " oK success"
   1208                 });
   1209         // If that fails, then try w/ parenthesis
   1210         mock.expectLiterally(
   1211                 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id (at) test.com>)",
   1212                 new String[] {
   1213                     "* sEARCH 321",
   1214                     getNextTag(true) + " oK success"
   1215                 });
   1216 
   1217         mFolder.appendMessages(new Message[] {message});
   1218 
   1219         assertEquals("321", message.getUid());
   1220     }
   1221 
   1222     /**
   1223      * Test for append failure.
   1224      *
   1225      * We don't check the response for APPEND.  We just SEARCH for the message-id to get the UID.
   1226      * If append has failed, the SEARCH command returns no UID, and the UID of the message is left
   1227      * unset.
   1228      */
   1229     public void testAppendFailure() throws Exception {
   1230         MockTransport mock = openAndInjectMockTransport();
   1231         setupOpenFolder(mock);
   1232         mFolder.open(OpenMode.READ_WRITE);
   1233 
   1234         ImapMessage message = prepareForAppendTest(mock, "NO No space left on the server.");
   1235         assertEquals("initial uid", message.getUid());
   1236         // First try w/o parenthesis
   1237         mock.expectLiterally(
   1238                 getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id (at) test.com>",
   1239                 new String[] {
   1240                     "* sEARCH", // not found
   1241                     getNextTag(true) + " oK Search completed."
   1242                 });
   1243         // If that fails, then try w/ parenthesis
   1244         mock.expectLiterally(
   1245                 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id (at) test.com>)",
   1246                 new String[] {
   1247                     "* sEARCH", // not found
   1248                     getNextTag(true) + " oK Search completed."
   1249                 });
   1250 
   1251         mFolder.appendMessages(new Message[] {message});
   1252 
   1253         // Shouldn't have changed
   1254         assertEquals("initial uid", message.getUid());
   1255     }
   1256 
   1257     public void testGetAllFolders() throws Exception {
   1258         MockTransport mock = openAndInjectMockTransport();
   1259         expectLogin(mock);
   1260 
   1261         expectNoop(mock, true);
   1262         mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
   1263                 new String[] {
   1264                 "* lIST (\\HAsNoChildren) \"/\" \"inbox\"",
   1265                 "* lIST (\\hAsnochildren) \"/\" \"Drafts\"",
   1266                 "* lIST (\\nOselect) \"/\" \"no select\"",
   1267                 "* lIST (\\HAsNoChildren) \"/\" \"&ZeVnLIqe-\"", // Japanese folder name
   1268                 getNextTag(true) + " oK SUCCESS"
   1269                 });
   1270         Folder[] folders = mStore.updateFolders();
   1271         ImapFolder testFolder;
   1272 
   1273         testFolder = (ImapFolder) folders[0];
   1274         assertEquals("INBOX", testFolder.getName());
   1275         assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
   1276 
   1277         testFolder = (ImapFolder) folders[1];
   1278         assertEquals("no select", testFolder.getName());
   1279         assertEquals(0, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
   1280 
   1281         testFolder = (ImapFolder) folders[2];
   1282         assertEquals("\u65E5\u672C\u8A9E", testFolder.getName());
   1283         assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
   1284 
   1285         testFolder = (ImapFolder) folders[3];
   1286         assertEquals("Drafts", testFolder.getName());
   1287         assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
   1288         // TODO test with path prefix
   1289         // TODO: Test NO response.
   1290     }
   1291 
   1292     public void testEncodeFolderName() {
   1293         // null prefix
   1294         assertEquals("",
   1295                 ImapStore.encodeFolderName("", null));
   1296         assertEquals("a",
   1297                 ImapStore.encodeFolderName("a", null));
   1298         assertEquals("XYZ",
   1299                 ImapStore.encodeFolderName("XYZ", null));
   1300         assertEquals("&ZeVnLIqe-",
   1301                 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", null));
   1302         assertEquals("!&ZeVnLIqe-!",
   1303                 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", null));
   1304         // empty prefix (same as a null prefix)
   1305         assertEquals("",
   1306                 ImapStore.encodeFolderName("", ""));
   1307         assertEquals("a",
   1308                 ImapStore.encodeFolderName("a", ""));
   1309         assertEquals("XYZ",
   1310                 ImapStore.encodeFolderName("XYZ", ""));
   1311         assertEquals("&ZeVnLIqe-",
   1312                 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", ""));
   1313         assertEquals("!&ZeVnLIqe-!",
   1314                 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", ""));
   1315         // defined prefix
   1316         assertEquals("[Gmail]/",
   1317                 ImapStore.encodeFolderName("", "[Gmail]/"));
   1318         assertEquals("[Gmail]/a",
   1319                 ImapStore.encodeFolderName("a", "[Gmail]/"));
   1320         assertEquals("[Gmail]/XYZ",
   1321                 ImapStore.encodeFolderName("XYZ", "[Gmail]/"));
   1322         assertEquals("[Gmail]/&ZeVnLIqe-",
   1323                 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", "[Gmail]/"));
   1324         assertEquals("[Gmail]/!&ZeVnLIqe-!",
   1325                 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", "[Gmail]/"));
   1326         // Add prefix to special mailbox "INBOX" [case insensitive), no affect
   1327         assertEquals("INBOX",
   1328                 ImapStore.encodeFolderName("INBOX", "[Gmail]/"));
   1329         assertEquals("inbox",
   1330                 ImapStore.encodeFolderName("inbox", "[Gmail]/"));
   1331         assertEquals("InBoX",
   1332                 ImapStore.encodeFolderName("InBoX", "[Gmail]/"));
   1333     }
   1334 
   1335     public void testDecodeFolderName() {
   1336         // null prefix
   1337         assertEquals("",
   1338                 ImapStore.decodeFolderName("", null));
   1339         assertEquals("a",
   1340                 ImapStore.decodeFolderName("a", null));
   1341         assertEquals("XYZ",
   1342                 ImapStore.decodeFolderName("XYZ", null));
   1343         assertEquals("\u65E5\u672C\u8A9E",
   1344                 ImapStore.decodeFolderName("&ZeVnLIqe-", null));
   1345         assertEquals("!\u65E5\u672C\u8A9E!",
   1346                 ImapStore.decodeFolderName("!&ZeVnLIqe-!", null));
   1347         // empty prefix (same as a null prefix)
   1348         assertEquals("",
   1349                 ImapStore.decodeFolderName("", ""));
   1350         assertEquals("a",
   1351                 ImapStore.decodeFolderName("a", ""));
   1352         assertEquals("XYZ",
   1353                 ImapStore.decodeFolderName("XYZ", ""));
   1354         assertEquals("\u65E5\u672C\u8A9E",
   1355                 ImapStore.decodeFolderName("&ZeVnLIqe-", ""));
   1356         assertEquals("!\u65E5\u672C\u8A9E!",
   1357                 ImapStore.decodeFolderName("!&ZeVnLIqe-!", ""));
   1358         // defined prefix; prefix found, prefix removed
   1359         assertEquals("",
   1360                 ImapStore.decodeFolderName("[Gmail]/", "[Gmail]/"));
   1361         assertEquals("a",
   1362                 ImapStore.decodeFolderName("[Gmail]/a", "[Gmail]/"));
   1363         assertEquals("XYZ",
   1364                 ImapStore.decodeFolderName("[Gmail]/XYZ", "[Gmail]/"));
   1365         assertEquals("\u65E5\u672C\u8A9E",
   1366                 ImapStore.decodeFolderName("[Gmail]/&ZeVnLIqe-", "[Gmail]/"));
   1367         assertEquals("!\u65E5\u672C\u8A9E!",
   1368                 ImapStore.decodeFolderName("[Gmail]/!&ZeVnLIqe-!", "[Gmail]/"));
   1369         // defined prefix; prefix not found, no affect
   1370         assertEquals("INBOX/",
   1371                 ImapStore.decodeFolderName("INBOX/", "[Gmail]/"));
   1372         assertEquals("INBOX/a",
   1373                 ImapStore.decodeFolderName("INBOX/a", "[Gmail]/"));
   1374         assertEquals("INBOX/XYZ",
   1375                 ImapStore.decodeFolderName("INBOX/XYZ", "[Gmail]/"));
   1376         assertEquals("INBOX/\u65E5\u672C\u8A9E",
   1377                 ImapStore.decodeFolderName("INBOX/&ZeVnLIqe-", "[Gmail]/"));
   1378         assertEquals("INBOX/!\u65E5\u672C\u8A9E!",
   1379                 ImapStore.decodeFolderName("INBOX/!&ZeVnLIqe-!", "[Gmail]/"));
   1380     }
   1381 
   1382     public void testEnsurePrefixIsValid() {
   1383         // Test mPathSeparator == null
   1384         mStore.mPathSeparator = null;
   1385         mStore.mPathPrefix = null;
   1386         mStore.ensurePrefixIsValid();
   1387         assertNull(mStore.mPathPrefix);
   1388 
   1389         mStore.mPathPrefix = "";
   1390         mStore.ensurePrefixIsValid();
   1391         assertEquals("", mStore.mPathPrefix);
   1392 
   1393         mStore.mPathPrefix = "foo";
   1394         mStore.ensurePrefixIsValid();
   1395         assertEquals("foo", mStore.mPathPrefix);
   1396 
   1397         mStore.mPathPrefix = "foo.";
   1398         mStore.ensurePrefixIsValid();
   1399         assertEquals("foo.", mStore.mPathPrefix);
   1400 
   1401         // Test mPathSeparator == ""
   1402         mStore.mPathSeparator = "";
   1403         mStore.mPathPrefix = null;
   1404         mStore.ensurePrefixIsValid();
   1405         assertNull(mStore.mPathPrefix);
   1406 
   1407         mStore.mPathPrefix = "";
   1408         mStore.ensurePrefixIsValid();
   1409         assertEquals("", mStore.mPathPrefix);
   1410 
   1411         mStore.mPathPrefix = "foo";
   1412         mStore.ensurePrefixIsValid();
   1413         assertEquals("foo", mStore.mPathPrefix);
   1414 
   1415         mStore.mPathPrefix = "foo.";
   1416         mStore.ensurePrefixIsValid();
   1417         assertEquals("foo.", mStore.mPathPrefix);
   1418 
   1419         // Test mPathSeparator is non-empty
   1420         mStore.mPathSeparator = ".";
   1421         mStore.mPathPrefix = null;
   1422         mStore.ensurePrefixIsValid();
   1423         assertNull(mStore.mPathPrefix);
   1424 
   1425         mStore.mPathPrefix = "";
   1426         mStore.ensurePrefixIsValid();
   1427         assertEquals("", mStore.mPathPrefix);
   1428 
   1429         mStore.mPathPrefix = "foo";
   1430         mStore.ensurePrefixIsValid();
   1431         assertEquals("foo.", mStore.mPathPrefix);
   1432 
   1433         // Trailing separator; path separator NOT appended
   1434         mStore.mPathPrefix = "foo.";
   1435         mStore.ensurePrefixIsValid();
   1436         assertEquals("foo.", mStore.mPathPrefix);
   1437 
   1438         // Trailing punctuation has no affect; path separator still appended
   1439         mStore.mPathPrefix = "foo/";
   1440         mStore.ensurePrefixIsValid();
   1441         assertEquals("foo/.", mStore.mPathPrefix);
   1442     }
   1443 
   1444     public void testOpen() throws Exception {
   1445         MockTransport mock = openAndInjectMockTransport();
   1446         expectLogin(mock);
   1447 
   1448         final Folder folder = mStore.getFolder("test");
   1449 
   1450         // Not exist
   1451         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1452                 new String[] {
   1453                 getNextTag(true) + " nO no such mailbox"
   1454                 });
   1455         try {
   1456             folder.open(OpenMode.READ_WRITE);
   1457             fail();
   1458         } catch (MessagingException expected) {
   1459         }
   1460 
   1461         // READ-WRITE
   1462         expectNoop(mock, true); // Need it because we reuse the connection.
   1463         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1464                 new String[] {
   1465                 "* 1 eXISTS",
   1466                 getNextTag(true) + " oK [rEAD-wRITE]"
   1467                 });
   1468 
   1469         folder.open(OpenMode.READ_WRITE);
   1470         assertTrue(folder.exists());
   1471         assertEquals(1, folder.getMessageCount());
   1472         assertEquals(OpenMode.READ_WRITE, folder.getMode());
   1473 
   1474         assertTrue(folder.isOpen());
   1475         folder.close(false);
   1476         assertFalse(folder.isOpen());
   1477 
   1478         // READ-ONLY
   1479         expectNoop(mock, true); // Need it because we reuse the connection.
   1480         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1481                 new String[] {
   1482                 "* 2 eXISTS",
   1483                 getNextTag(true) + " oK [rEAD-oNLY]"
   1484                 });
   1485 
   1486         folder.open(OpenMode.READ_WRITE);
   1487         assertTrue(folder.exists());
   1488         assertEquals(2, folder.getMessageCount());
   1489         assertEquals(OpenMode.READ_ONLY, folder.getMode());
   1490 
   1491         // Try to re-open as read-write.  Should send SELECT again.
   1492         expectNoop(mock, true); // Need it because we reuse the connection.
   1493         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1494                 new String[] {
   1495                 "* 15 eXISTS",
   1496                 getNextTag(true) + " oK selected"
   1497                 });
   1498 
   1499         folder.open(OpenMode.READ_WRITE);
   1500         assertTrue(folder.exists());
   1501         assertEquals(15, folder.getMessageCount());
   1502         assertEquals(OpenMode.READ_WRITE, folder.getMode());
   1503     }
   1504 
   1505     public void testExists() throws Exception {
   1506         MockTransport mock = openAndInjectMockTransport();
   1507         expectLogin(mock);
   1508 
   1509         // Folder exists
   1510         Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
   1511         mock.expect(getNextTag(false) + " STATUS \\\"&ZeVnLIqe-\\\" \\(UIDVALIDITY\\)",
   1512                 new String[] {
   1513                 "* sTATUS \"&ZeVnLIqe-\" (mESSAGES 10)",
   1514                 getNextTag(true) + " oK SUCCESS"
   1515                 });
   1516 
   1517         assertTrue(folder.exists());
   1518 
   1519         // Connection verification
   1520         expectNoop(mock, true);
   1521 
   1522         // Doesn't exist
   1523         folder = mStore.getFolder("no such folder");
   1524         mock.expect(getNextTag(false) + " STATUS \\\"no such folder\\\" \\(UIDVALIDITY\\)",
   1525                 new String[] {
   1526                 getNextTag(true) + " NO No such folder!"
   1527                 });
   1528 
   1529         assertFalse(folder.exists());
   1530     }
   1531 
   1532     public void testCreate() throws Exception {
   1533         MockTransport mock = openAndInjectMockTransport();
   1534         expectLogin(mock);
   1535 
   1536         // Success
   1537         Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
   1538 
   1539         assertTrue(folder.canCreate(FolderType.HOLDS_MESSAGES));
   1540 
   1541         mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
   1542                 new String[] {
   1543                 getNextTag(true) + " oK Success"
   1544                 });
   1545 
   1546         assertTrue(folder.create(FolderType.HOLDS_MESSAGES));
   1547 
   1548         // Connection verification
   1549         expectNoop(mock, true);
   1550 
   1551         // Failure
   1552         mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
   1553                 new String[] {
   1554                 getNextTag(true) + " nO Can't create folder"
   1555                 });
   1556 
   1557         assertFalse(folder.create(FolderType.HOLDS_MESSAGES));
   1558     }
   1559 
   1560     private void setupCopyMessages(boolean withUidPlus) throws Exception {
   1561         mCopyMock = openAndInjectMockTransport();
   1562         setupOpenFolder(mCopyMock, new String[] {"* iD nIL", "oK"}, "rEAD-wRITE", withUidPlus);
   1563         mFolder.open(OpenMode.READ_WRITE);
   1564 
   1565         mCopyToFolder = mStore.getFolder("\u65E5\u672C\u8A9E");
   1566         Message m1 = mFolder.createMessage("11");
   1567         m1.setMessageId("<4D8978AE.0000005D (at) m58.foo.com>");
   1568         Message m2 = mFolder.createMessage("12");
   1569         m2.setMessageId("<549373104MSOSI1:145OSIMS (at) bar.com>");
   1570         mCopyMessages = new Message[] { m1, m2 };
   1571     }
   1572 
   1573     /**
   1574      * Returns the pattern for the IMAP request to copy messages.
   1575      */
   1576     private String getCopyMessagesPattern() {
   1577         return getNextTag(false) + " UID COPY 11\\,12 \\\"&ZeVnLIqe-\\\"";
   1578     }
   1579 
   1580     /**
   1581      * Returns the pattern for the IMAP request to search for messages based on Message-Id.
   1582      */
   1583     private String getSearchMessagesPattern(String messageId) {
   1584         return getNextTag(false) + " UID SEARCH HEADER Message-Id \"" + messageId + "\"";
   1585     }
   1586 
   1587     /**
   1588      * Counts the number of times the callback methods are invoked.
   1589      */
   1590     private static class MessageUpdateCallbackCounter implements Folder.MessageUpdateCallbacks {
   1591         int messageNotFoundCalled;
   1592         int messageUidChangeCalled;
   1593 
   1594         @Override
   1595         public void onMessageNotFound(Message message) {
   1596             ++messageNotFoundCalled;
   1597         }
   1598         @Override
   1599         public void onMessageUidChange(Message message, String newUid) {
   1600             ++messageUidChangeCalled;
   1601         }
   1602     }
   1603 
   1604     // TODO Test additional degenerate cases; src msg not found, ...
   1605     // Golden case; successful copy with UIDCOPY result
   1606     public void testCopyMessages1() throws Exception {
   1607         setupCopyMessages(true);
   1608         mCopyMock.expect(getCopyMessagesPattern(),
   1609                 new String[] {
   1610                     "* Ok COPY in progress",
   1611                     getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed"
   1612                 });
   1613 
   1614         MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
   1615         mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
   1616 
   1617         assertEquals(0, cb.messageNotFoundCalled);
   1618         assertEquals(2, cb.messageUidChangeCalled);
   1619     }
   1620 
   1621     // Degenerate case; NO, un-tagged response works
   1622     public void testCopyMessages2() throws Exception {
   1623         setupCopyMessages(true);
   1624         mCopyMock.expect(getCopyMessagesPattern(),
   1625                 new String[] {
   1626                     "* No Some error occured during the copy",
   1627                     getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed"
   1628                 });
   1629 
   1630         MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
   1631         mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
   1632 
   1633         assertEquals(0, cb.messageNotFoundCalled);
   1634         assertEquals(2, cb.messageUidChangeCalled);
   1635     }
   1636 
   1637     // Degenerate case; NO, tagged response throws MessagingException
   1638     public void testCopyMessages3() throws Exception {
   1639         try {
   1640             setupCopyMessages(false);
   1641             mCopyMock.expect(getCopyMessagesPattern(),
   1642                     new String[] {
   1643                         getNextTag(true) + " No copy did not finish"
   1644                     });
   1645 
   1646             mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
   1647 
   1648             fail("MessagingException expected.");
   1649         } catch (MessagingException expected) {
   1650         }
   1651     }
   1652 
   1653     // Degenerate case; BAD, un-tagged response throws MessagingException
   1654     public void testCopyMessages4() throws Exception {
   1655         try {
   1656             setupCopyMessages(true);
   1657             mCopyMock.expect(getCopyMessagesPattern(),
   1658                     new String[] {
   1659                         "* BAD failed for some reason",
   1660                         getNextTag(true) + " Ok copy completed"
   1661                     });
   1662 
   1663             mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
   1664 
   1665             fail("MessagingException expected.");
   1666         } catch (MessagingException expected) {
   1667         }
   1668     }
   1669 
   1670     // Degenerate case; BAD, tagged response throws MessagingException
   1671     public void testCopyMessages5() throws Exception {
   1672         try {
   1673             setupCopyMessages(false);
   1674             mCopyMock.expect(getCopyMessagesPattern(),
   1675                     new String[] {
   1676                         getNextTag(true) + " BaD copy completed"
   1677                     });
   1678 
   1679             mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
   1680 
   1681             fail("MessagingException expected.");
   1682         } catch (MessagingException expected) {
   1683         }
   1684     }
   1685 
   1686     // Golden case; successful copy getting UIDs via search
   1687     public void testCopyMessages6() throws Exception {
   1688         setupCopyMessages(false);
   1689         mCopyMock.expect(getCopyMessagesPattern(),
   1690                 new String[] {
   1691                     getNextTag(true) + " oK UID COPY completed",
   1692                 });
   1693         // New connection, so, we need to login again & the tag count gets reset
   1694         int saveTag = resetTag();
   1695         expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
   1696         // Select destination folder
   1697         expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
   1698         // Perform searches
   1699         mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D (at) m58.foo.com>"),
   1700                 new String[] {
   1701                     "* SeArCh 777",
   1702                     getNextTag(true) + " oK UID SEARCH completed (1 msgs in 3.14159 secs)",
   1703                 });
   1704         mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS (at) bar.com>"),
   1705                 new String[] {
   1706                     "* sEaRcH 1818",
   1707                     getNextTag(true) + " oK UID SEARCH completed (1 msgs in 2.71828 secs)",
   1708                 });
   1709         // Resume commands on the initial connection
   1710         resetTag(saveTag);
   1711         // Select the original folder
   1712         expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
   1713 
   1714         MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
   1715         mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
   1716 
   1717         assertEquals(0, cb.messageNotFoundCalled);
   1718         assertEquals(2, cb.messageUidChangeCalled);
   1719     }
   1720 
   1721     // Degenerate case; searches turn up nothing
   1722     public void testCopyMessages7() throws Exception {
   1723         setupCopyMessages(false);
   1724         mCopyMock.expect(getCopyMessagesPattern(),
   1725                 new String[] {
   1726                     getNextTag(true) + " oK UID COPY completed",
   1727                 });
   1728         // New connection, so, we need to login again & the tag count gets reset
   1729         int saveTag = resetTag();
   1730         expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
   1731         // Select destination folder
   1732         expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
   1733         // Perform searches
   1734         mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D (at) m58.foo.com>"),
   1735                 new String[] {
   1736                     "* SeArCh",
   1737                     getNextTag(true) + " oK UID SEARCH completed (0 msgs in 6.02214 secs)",
   1738                 });
   1739         mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS (at) bar.com>"),
   1740                 new String[] {
   1741                     "* sEaRcH",
   1742                     getNextTag(true) + " oK UID SEARCH completed (0 msgs in 2.99792 secs)",
   1743                 });
   1744         // Resume commands on the initial connection
   1745         resetTag(saveTag);
   1746         // Select the original folder
   1747         expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
   1748 
   1749         MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
   1750         mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
   1751 
   1752         assertEquals(0, cb.messageNotFoundCalled);
   1753         assertEquals(0, cb.messageUidChangeCalled);
   1754     }
   1755 
   1756     // Degenerate case; search causes an exception; must be eaten
   1757     public void testCopyMessages8() throws Exception {
   1758         setupCopyMessages(false);
   1759         mCopyMock.expect(getCopyMessagesPattern(),
   1760                 new String[] {
   1761                     getNextTag(true) + " oK UID COPY completed",
   1762                 });
   1763         // New connection, so, we need to login again & the tag count gets reset
   1764         int saveTag = resetTag();
   1765         expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
   1766         // Select destination folder
   1767         expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
   1768         // Perform searches
   1769         mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D (at) m58.foo.com>"),
   1770                 new String[] {
   1771                     getNextTag(true) + " BaD search failed"
   1772                 });
   1773         mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS (at) bar.com>"),
   1774                 new String[] {
   1775                     getNextTag(true) + " BaD search failed"
   1776                 });
   1777         // Resume commands on the initial connection
   1778         resetTag(saveTag);
   1779         // Select the original folder
   1780         expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
   1781 
   1782         MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
   1783         mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
   1784 
   1785         assertEquals(0, cb.messageNotFoundCalled);
   1786         assertEquals(0, cb.messageUidChangeCalled);
   1787     }
   1788 
   1789     public void testGetUnreadMessageCount() throws Exception {
   1790         MockTransport mock = openAndInjectMockTransport();
   1791         setupOpenFolder(mock);
   1792         mFolder.open(OpenMode.READ_WRITE);
   1793 
   1794         mock.expect(getNextTag(false) + " STATUS \\\"" + FOLDER_ENCODED + "\\\" \\(UNSEEN\\)",
   1795                 new String[] {
   1796                 "* sTATUS \"" + FOLDER_ENCODED + "\" (X 1 uNSEEN 123)",
   1797                 getNextTag(true) + " oK copy completed"
   1798                 });
   1799 
   1800         assertEquals(123, mFolder.getUnreadMessageCount());
   1801     }
   1802 
   1803     public void testExpunge() throws Exception {
   1804         MockTransport mock = openAndInjectMockTransport();
   1805         setupOpenFolder(mock);
   1806         mFolder.open(OpenMode.READ_WRITE);
   1807 
   1808         mock.expect(getNextTag(false) + " EXPUNGE",
   1809                 new String[] {
   1810                 getNextTag(true) + " oK success"
   1811                 });
   1812 
   1813         mFolder.expunge();
   1814 
   1815         // TODO: Test NO response. (permission denied)
   1816     }
   1817 
   1818     public void testSetFlags() throws Exception {
   1819         MockTransport mock = openAndInjectMockTransport();
   1820         setupOpenFolder(mock);
   1821         mFolder.open(OpenMode.READ_WRITE);
   1822 
   1823         Message[] messages = new Message[] {
   1824                 mFolder.createMessage("11"),
   1825                 mFolder.createMessage("12"),
   1826                 };
   1827 
   1828         // Set
   1829         mock.expect(
   1830                 getNextTag(false) + " UID STORE 11\\,12 \\+FLAGS.SILENT \\(\\\\FLAGGED \\\\SEEN\\)",
   1831                 new String[] {
   1832                 getNextTag(true) + " oK success"
   1833                 });
   1834         mFolder.setFlags(messages, new Flag[] {Flag.FLAGGED, Flag.SEEN}, true);
   1835 
   1836         // Clear
   1837         mock.expect(
   1838                 getNextTag(false) + " UID STORE 11\\,12 \\-FLAGS.SILENT \\(\\\\DELETED\\)",
   1839                 new String[] {
   1840                 getNextTag(true) + " oK success"
   1841                 });
   1842         mFolder.setFlags(messages, new Flag[] {Flag.DELETED}, false);
   1843 
   1844         // TODO: Test NO response. (src message not found)
   1845     }
   1846 
   1847     public void testSearchForUids() throws Exception {
   1848         MockTransport mock = openAndInjectMockTransport();
   1849         setupOpenFolder(mock);
   1850         mFolder.open(OpenMode.READ_WRITE);
   1851 
   1852         // Single results
   1853         mock.expect(
   1854                 getNextTag(false) + " UID SEARCH X",
   1855                 new String[] {
   1856                         "* sEARCH 1",
   1857                         getNextTag(true) + " oK success"
   1858                 });
   1859         MoreAsserts.assertEquals(new String[] {
   1860                 "1"
   1861                 }, mFolder.searchForUids("X"));
   1862 
   1863         // Multiple results, including SEARCH with no UIDs.
   1864         mock.expect(
   1865                 getNextTag(false) + " UID SEARCH UID 123",
   1866                 new String[] {
   1867                         "* sEARCH 123 4 567",
   1868                         "* search",
   1869                         "* sEARCH 0",
   1870                         "* SEARCH",
   1871                         "* sEARCH 100 200 300",
   1872                         getNextTag(true) + " oK success"
   1873                 });
   1874         MoreAsserts.assertEquals(new String[] {
   1875                 "123", "4", "567", "0", "100", "200", "300"
   1876                 }, mFolder.searchForUids("UID 123"));
   1877 
   1878         // NO result
   1879         mock.expect(
   1880                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1881                 new String[] {
   1882                         getNextTag(true) + " nO not found"
   1883                 });
   1884         MoreAsserts.assertEquals(new String[] {
   1885                 }, mFolder.searchForUids("SOME CRITERIA"));
   1886 
   1887         // OK result, but result is empty. (Probably against RFC)
   1888         mock.expect(
   1889                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1890                 new String[] {
   1891                         getNextTag(true) + " oK success"
   1892                 });
   1893         MoreAsserts.assertEquals(new String[] {
   1894                 }, mFolder.searchForUids("SOME CRITERIA"));
   1895 
   1896         // OK result with empty search response.
   1897         mock.expect(
   1898                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1899                 new String[] {
   1900                         "* search",
   1901                         getNextTag(true) + " oK success"
   1902                 });
   1903         MoreAsserts.assertEquals(new String[] {
   1904                 }, mFolder.searchForUids("SOME CRITERIA"));
   1905     }
   1906 
   1907 
   1908     public void testGetMessage() throws Exception {
   1909         MockTransport mock = openAndInjectMockTransport();
   1910         setupOpenFolder(mock);
   1911         mFolder.open(OpenMode.READ_WRITE);
   1912 
   1913         // Found
   1914         mock.expect(
   1915                 getNextTag(false) + " UID SEARCH UID 123",
   1916                 new String[] {
   1917                     "* sEARCH 123",
   1918                 getNextTag(true) + " oK success"
   1919                 });
   1920         assertEquals("123", mFolder.getMessage("123").getUid());
   1921 
   1922         // Not found
   1923         mock.expect(
   1924                 getNextTag(false) + " UID SEARCH UID 123",
   1925                 new String[] {
   1926                 getNextTag(true) + " nO not found"
   1927                 });
   1928         assertNull(mFolder.getMessage("123"));
   1929     }
   1930 
   1931     /** Test for getMessages(int, int, MessageRetrievalListener) */
   1932     public void testGetMessages1() throws Exception {
   1933         MockTransport mock = openAndInjectMockTransport();
   1934         setupOpenFolder(mock);
   1935         mFolder.open(OpenMode.READ_WRITE);
   1936 
   1937         // Found
   1938         mock.expect(
   1939                 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
   1940                 new String[] {
   1941                     "* sEARCH 3 4",
   1942                 getNextTag(true) + " oK success"
   1943                 });
   1944 
   1945         checkMessageUids(new String[] {"3", "4"}, mFolder.getMessages(3, 5, null));
   1946 
   1947         // Not found
   1948         mock.expect(
   1949                 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
   1950                 new String[] {
   1951                 getNextTag(true) + " nO not found"
   1952                 });
   1953 
   1954         checkMessageUids(new String[] {}, mFolder.getMessages(3, 5, null));
   1955     }
   1956 
   1957     /**
   1958      * Test for getMessages(String[] uids, MessageRetrievalListener) where uids != null.
   1959      * (testGetMessages3() covers the case where uids == null.)
   1960      */
   1961     public void testGetMessages2() throws Exception {
   1962         MockTransport mock = openAndInjectMockTransport();
   1963         setupOpenFolder(mock);
   1964         mFolder.open(OpenMode.READ_WRITE);
   1965 
   1966         // No command will be sent
   1967         checkMessageUids(new String[] {"3", "4", "5"},
   1968                 mFolder.getMessages(new String[] {"3", "4", "5"}, null));
   1969 
   1970         checkMessageUids(new String[] {},
   1971                 mFolder.getMessages(new String[] {}, null));
   1972     }
   1973 
   1974     private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) {
   1975         ArrayList<String> list = new ArrayList<String>();
   1976         for (Message m : actualMessages) {
   1977             list.add(m.getUid());
   1978         }
   1979         MoreAsserts.assertEquals(expectedUids, list.toArray(new String[0]) );
   1980     }
   1981 
   1982     /**
   1983      * Test for {@link ImapStore#getConnection}
   1984      */
   1985     public void testGetConnection() throws Exception {
   1986         MockTransport mock = openAndInjectMockTransport();
   1987 
   1988         // Start: No pooled connections.
   1989         assertEquals(0, mStore.getConnectionPoolForTest().size());
   1990 
   1991         // Get 1st connection.
   1992         final ImapConnection con1 = mStore.getConnection();
   1993         assertNotNull(con1);
   1994         assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
   1995         assertFalse(con1.isTransportOpenForTest()); // Transport not open yet.
   1996 
   1997         // Open con1
   1998         expectLogin(mock);
   1999         con1.open();
   2000         assertTrue(con1.isTransportOpenForTest());
   2001 
   2002         // Get 2nd connection.
   2003         final ImapConnection con2 = mStore.getConnection();
   2004         assertNotNull(con2);
   2005         assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
   2006         assertFalse(con2.isTransportOpenForTest()); // Transport not open yet.
   2007 
   2008         // con1 != con2
   2009         assertNotSame(con1, con2);
   2010 
   2011         // New connection, so, we need to login again & the tag count gets reset
   2012         int saveTag = resetTag();
   2013 
   2014         // Open con2
   2015         expectLogin(mock);
   2016         con2.open();
   2017         assertTrue(con1.isTransportOpenForTest());
   2018 
   2019         // Now we have two open connections: con1 and con2
   2020 
   2021         // Save con1 in the pool.
   2022         mStore.poolConnection(con1);
   2023         assertEquals(1, mStore.getConnectionPoolForTest().size());
   2024 
   2025         // Get another connection.  Should get con1, after verifying the connection.
   2026         saveTag = resetTag(saveTag);
   2027         mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + " oK success"});
   2028 
   2029         final ImapConnection con1b = mStore.getConnection();
   2030         assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
   2031         assertSame(con1, con1b);
   2032         assertTrue(con1.isTransportOpenForTest()); // We opened it.
   2033 
   2034         // Save con2.
   2035         mStore.poolConnection(con2);
   2036         assertEquals(1, mStore.getConnectionPoolForTest().size());
   2037 
   2038         // Resume con2 tags ...
   2039         resetTag(saveTag);
   2040 
   2041         // Try to get connection, but this time, connection gets closed.
   2042         mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + "* bYE bye"});
   2043         final ImapConnection con3 = mStore.getConnection();
   2044         assertNotNull(con3);
   2045         assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
   2046 
   2047         // It should be a new connection.
   2048         assertNotSame(con1, con3);
   2049         assertNotSame(con2, con3);
   2050     }
   2051 
   2052     public void testCheckSettings() throws Exception {
   2053         MockTransport mock = openAndInjectMockTransport();
   2054 
   2055         expectLogin(mock);
   2056         mStore.checkSettings();
   2057 
   2058         resetTag();
   2059         expectLogin(mock, false, false, false,
   2060                 new String[] {"* iD nIL", "oK"}, "nO authentication failed");
   2061         try {
   2062             mStore.checkSettings();
   2063             fail();
   2064         } catch (MessagingException expected) {
   2065         }
   2066     }
   2067 
   2068     // Compatibility tests...
   2069 
   2070     /**
   2071      * Getting an ALERT with a % mark in the message, which crashed the old parser.
   2072      */
   2073     public void testQuotaAlert() throws Exception {
   2074         MockTransport mock = openAndInjectMockTransport();
   2075         expectLogin(mock);
   2076 
   2077         // Success
   2078         Folder folder = mStore.getFolder("INBOX");
   2079 
   2080         // The following response was copied from an actual bug...
   2081         mock.expect(getNextTag(false) + " SELECT \"INBOX\"", new String[] {
   2082             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk $Forwarded Junk" +
   2083                     " $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old)",
   2084             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk" +
   2085                     " $Forwarded Junk $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old \\*)]",
   2086             "* 6406 EXISTS",
   2087             "* 0 RECENT",
   2088             "* OK [UNSEEN 5338]",
   2089             "* OK [UIDVALIDITY 1055957975]",
   2090             "* OK [UIDNEXT 449625]",
   2091             "* NO [ALERT] Mailbox is at 98% of quota",
   2092             getNextTag(true) + " OK [READ-WRITE] Completed"});
   2093         folder.open(OpenMode.READ_WRITE); // shouldn't crash.
   2094         assertEquals(6406, folder.getMessageCount());
   2095     }
   2096 
   2097     /**
   2098      * Apparently some servers send a size in the wrong format. e.g. 123E
   2099      */
   2100     public void testFetchBodyStructureMalformed() throws Exception {
   2101         final MockTransport mock = openAndInjectMockTransport();
   2102         setupOpenFolder(mock);
   2103         mFolder.open(OpenMode.READ_WRITE);
   2104         final Message message = mFolder.createMessage("1");
   2105 
   2106         final FetchProfile fp = new FetchProfile();
   2107         fp.add(FetchProfile.Item.STRUCTURE);
   2108         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
   2109                 new String[] {
   2110                 "* 9 FETCH (UID 1 BODYSTRUCTURE (\"TEXT\" \"PLAIN\" ()" +
   2111                         " NIL NIL NIL 123E 3))", // 123E isn't a number!
   2112                 getNextTag(true) + " OK SUCCESS"
   2113         });
   2114         mFolder.fetch(new Message[] { message }, fp, null);
   2115 
   2116         // Check mime structure...
   2117         MoreAsserts.assertEquals(
   2118                 new String[] {"text/plain"},
   2119                 message.getHeader("Content-Type")
   2120                 );
   2121         assertNull(message.getHeader("Content-Transfer-Encoding"));
   2122         assertNull(message.getHeader("Content-ID"));
   2123 
   2124         // Doesn't have size=xxx
   2125         assertNull(message.getHeader("Content-Disposition"));
   2126     }
   2127 
   2128     /**
   2129      * Folder name with special chars in it.
   2130      *
   2131      * Gmail puts the folder name in the OK response, which crashed the old parser if there's a
   2132      * special char in the folder name.
   2133      */
   2134     public void testFolderNameWithSpecialChars() throws Exception {
   2135         final String FOLDER_1 = "@u88**%_St";
   2136         final String FOLDER_1_QUOTED = Pattern.quote(FOLDER_1);
   2137         final String FOLDER_2 = "folder test_06";
   2138 
   2139         MockTransport mock = openAndInjectMockTransport();
   2140         expectLogin(mock);
   2141 
   2142         // List folders.
   2143         expectNoop(mock, true);
   2144         mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
   2145                 new String[] {
   2146             "* LIST () \"/\" \"" + FOLDER_1 + "\"",
   2147             "* LIST () \"/\" \"" + FOLDER_2 + "\"",
   2148             getNextTag(true) + " OK SUCCESS"
   2149         });
   2150         final Folder[] folders = mStore.updateFolders();
   2151 
   2152         ArrayList<String> list = new ArrayList<String>();
   2153         for (Folder f : folders) {
   2154             list.add(f.getName());
   2155         }
   2156         MoreAsserts.assertEquals(
   2157                 new String[] {"INBOX", FOLDER_2, FOLDER_1},
   2158                 list.toArray(new String[0])
   2159                 );
   2160 
   2161         // Try to open the folders.
   2162         expectNoop(mock, true);
   2163         mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_1_QUOTED + "\"", new String[] {
   2164             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
   2165             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
   2166             "* 0 EXISTS",
   2167             "* 0 RECENT",
   2168             "* OK [UNSEEN 0]",
   2169             "* OK [UIDNEXT 1]",
   2170             getNextTag(true) + " OK [READ-WRITE] " + FOLDER_1});
   2171         folders[2].open(OpenMode.READ_WRITE);
   2172         folders[2].close(false);
   2173 
   2174         expectNoop(mock, true);
   2175         mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_2 + "\"", new String[] {
   2176             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
   2177             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
   2178             "* 0 EXISTS",
   2179             "* 0 RECENT",
   2180             "* OK [UNSEEN 0]",
   2181             "* OK [UIDNEXT 1]",
   2182             getNextTag(true) + " OK [READ-WRITE] " + FOLDER_2});
   2183         folders[1].open(OpenMode.READ_WRITE);
   2184         folders[1].close(false);
   2185     }
   2186 
   2187     /**
   2188      * Callback for {@link #runAndExpectMessagingException}.
   2189      */
   2190     private interface RunAndExpectMessagingExceptionTarget {
   2191         public void run(MockTransport mockTransport) throws Exception;
   2192     }
   2193 
   2194     /**
   2195      * Set up the usual mock transport, open the folder,
   2196      * run {@link RunAndExpectMessagingExceptionTarget} and make sure a {@link MessagingException}
   2197      * is thrown.
   2198      */
   2199     private void runAndExpectMessagingException(RunAndExpectMessagingExceptionTarget target)
   2200             throws Exception {
   2201         try {
   2202             final MockTransport mockTransport = openAndInjectMockTransport();
   2203             setupOpenFolder(mockTransport);
   2204             mFolder.open(OpenMode.READ_WRITE);
   2205 
   2206             target.run(mockTransport);
   2207 
   2208             fail("MessagingException expected.");
   2209         } catch (MessagingException expected) {
   2210         }
   2211     }
   2212 
   2213     /**
   2214      * Make sure that IOExceptions are always converted to MessagingException.
   2215      */
   2216     public void testFetchIOException() throws Exception {
   2217         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2218             @Override
   2219             public void run(MockTransport mockTransport) throws Exception {
   2220                 mockTransport.expectIOException();
   2221 
   2222                 final Message message = mFolder.createMessage("1");
   2223                 final FetchProfile fp = new FetchProfile();
   2224                 fp.add(FetchProfile.Item.STRUCTURE);
   2225 
   2226                 mFolder.fetch(new Message[] { message }, fp, null);
   2227             }
   2228         });
   2229     }
   2230 
   2231     /**
   2232      * Make sure that IOExceptions are always converted to MessagingException.
   2233      */
   2234     public void testUnreadMessageCountIOException() throws Exception {
   2235         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2236             @Override
   2237             public void run(MockTransport mockTransport) throws Exception {
   2238                 mockTransport.expectIOException();
   2239 
   2240                 mFolder.getUnreadMessageCount();
   2241             }
   2242         });
   2243     }
   2244 
   2245     /**
   2246      * Make sure that IOExceptions are always converted to MessagingException.
   2247      */
   2248     public void testCopyMessagesIOException() throws Exception {
   2249         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2250             @Override
   2251             public void run(MockTransport mockTransport) throws Exception {
   2252                 mockTransport.expectIOException();
   2253 
   2254                 final Message message = mFolder.createMessage("1");
   2255                 final Folder folder = mStore.getFolder("test");
   2256 
   2257                 mFolder.copyMessages(new Message[] { message }, folder, null);
   2258             }
   2259         });
   2260     }
   2261 
   2262     /**
   2263      * Make sure that IOExceptions are always converted to MessagingException.
   2264      */
   2265     public void testSearchForUidsIOException() throws Exception {
   2266         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2267             @Override
   2268             public void run(MockTransport mockTransport) throws Exception {
   2269                 mockTransport.expectIOException();
   2270 
   2271                 mFolder.getMessage("uid");
   2272             }
   2273         });
   2274     }
   2275 
   2276     /**
   2277      * Make sure that IOExceptions are always converted to MessagingException.
   2278      */
   2279     public void testExpungeIOException() throws Exception {
   2280         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2281             @Override
   2282             public void run(MockTransport mockTransport) throws Exception {
   2283                 mockTransport.expectIOException();
   2284 
   2285                 mFolder.expunge();
   2286             }
   2287         });
   2288     }
   2289 
   2290     /**
   2291      * Make sure that IOExceptions are always converted to MessagingException.
   2292      */
   2293     public void testOpenIOException() throws Exception {
   2294         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   2295             @Override
   2296             public void run(MockTransport mockTransport) throws Exception {
   2297                 mockTransport.expectIOException();
   2298                 final Folder folder = mStore.getFolder("test");
   2299                 folder.open(OpenMode.READ_WRITE);
   2300             }
   2301         });
   2302     }
   2303 
   2304     /** Creates a folder & mailbox */
   2305     private ImapFolder createFolder(long id, String displayName, String serverId, char delimiter) {
   2306         ImapFolder folder = new ImapFolder(null, serverId);
   2307         Mailbox mailbox = new Mailbox();
   2308         mailbox.mId = id;
   2309         mailbox.mDisplayName = displayName;
   2310         mailbox.mServerId = serverId;
   2311         mailbox.mDelimiter = delimiter;
   2312         mailbox.mFlags = 0xAAAAAAA8;
   2313         folder.mMailbox = mailbox;
   2314         return folder;
   2315     }
   2316 
   2317     /** Tests creating folder hierarchies */
   2318     public void testCreateHierarchy() {
   2319         HashMap<String, ImapFolder> testMap = new HashMap<String, ImapFolder>();
   2320 
   2321         // Create hierarchy
   2322         //   |-INBOX
   2323         //   |  +-b
   2324         //   |-a
   2325         //   |  |-b
   2326         //   |  |-c
   2327         //   |  +-d
   2328         //   |    +-b
   2329         //   |      +-b
   2330         //   +-g
   2331         ImapFolder[] folders = {
   2332             createFolder(1L, "INBOX", "INBOX", '/'),
   2333             createFolder(2L, "b", "INBOX/b", '/'),
   2334             createFolder(3L, "a", "a", '/'),
   2335             createFolder(4L, "b", "a/b", '/'),
   2336             createFolder(5L, "c", "a/c", '/'),
   2337             createFolder(6L, "d", "a/d", '/'),
   2338             createFolder(7L, "b", "a/d/b", '/'),
   2339             createFolder(8L, "b", "a/d/b/b", '/'),
   2340             createFolder(9L, "g", "g", '/'),
   2341         };
   2342         for (ImapFolder folder : folders) {
   2343             testMap.put(folder.getName(), folder);
   2344         }
   2345 
   2346         ImapStore.createHierarchy(testMap);
   2347         // 'INBOX'
   2348         assertEquals(-1L, folders[0].mMailbox.mParentKey);
   2349         assertEquals(0xAAAAAAAB, folders[0].mMailbox.mFlags);
   2350         // 'INBOX/b'
   2351         assertEquals(1L, folders[1].mMailbox.mParentKey);
   2352         assertEquals(0xAAAAAAA8, folders[1].mMailbox.mFlags);
   2353         // 'a'
   2354         assertEquals(-1L, folders[2].mMailbox.mParentKey);
   2355         assertEquals(0xAAAAAAAB, folders[2].mMailbox.mFlags);
   2356         // 'a/b'
   2357         assertEquals(3L, folders[3].mMailbox.mParentKey);
   2358         assertEquals(0xAAAAAAA8, folders[3].mMailbox.mFlags);
   2359         // 'a/c'
   2360         assertEquals(3L, folders[4].mMailbox.mParentKey);
   2361         assertEquals(0xAAAAAAA8, folders[4].mMailbox.mFlags);
   2362         // 'a/d'
   2363         assertEquals(3L, folders[5].mMailbox.mParentKey);
   2364         assertEquals(0xAAAAAAAB, folders[5].mMailbox.mFlags);
   2365         // 'a/d/b'
   2366         assertEquals(6L, folders[6].mMailbox.mParentKey);
   2367         assertEquals(0xAAAAAAAB, folders[6].mMailbox.mFlags);
   2368         // 'a/d/b/b'
   2369         assertEquals(7L, folders[7].mMailbox.mParentKey);
   2370         assertEquals(0xAAAAAAA8, folders[7].mMailbox.mFlags);
   2371         // 'g'
   2372         assertEquals(-1L, folders[8].mMailbox.mParentKey);
   2373         assertEquals(0xAAAAAAA8, folders[8].mMailbox.mFlags);
   2374     }
   2375 }
   2376