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