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 com.android.email.Email;
     20 import com.android.email.MockVendorPolicy;
     21 import com.android.email.Utility;
     22 import com.android.email.VendorPolicyLoader;
     23 import com.android.email.mail.Address;
     24 import com.android.email.mail.AuthenticationFailedException;
     25 import com.android.email.mail.Body;
     26 import com.android.email.mail.FetchProfile;
     27 import com.android.email.mail.Flag;
     28 import com.android.email.mail.Folder;
     29 import com.android.email.mail.Message;
     30 import com.android.email.mail.MessagingException;
     31 import com.android.email.mail.Part;
     32 import com.android.email.mail.Transport;
     33 import com.android.email.mail.Folder.FolderType;
     34 import com.android.email.mail.Folder.OpenMode;
     35 import com.android.email.mail.Message.RecipientType;
     36 import com.android.email.mail.internet.MimeBodyPart;
     37 import com.android.email.mail.internet.MimeMultipart;
     38 import com.android.email.mail.internet.MimeUtility;
     39 import com.android.email.mail.internet.TextBody;
     40 import com.android.email.mail.store.ImapStore.ImapConnection;
     41 import com.android.email.mail.store.ImapStore.ImapMessage;
     42 import com.android.email.mail.store.imap.ImapResponse;
     43 import com.android.email.mail.store.imap.ImapTestUtils;
     44 import com.android.email.mail.transport.MockTransport;
     45 
     46 import org.apache.commons.io.IOUtils;
     47 
     48 import android.os.Bundle;
     49 import android.test.AndroidTestCase;
     50 import android.test.MoreAsserts;
     51 import android.test.suitebuilder.annotation.SmallTest;
     52 
     53 import java.io.IOException;
     54 import java.util.ArrayList;
     55 import java.util.HashMap;
     56 import java.util.regex.Pattern;
     57 
     58 /**
     59  * This is a series of unit tests for the ImapStore class.  These tests must be locally
     60  * complete - no server(s) required.
     61  *
     62  * To run these tests alone, use:
     63  *   $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
     64  *
     65  * TODO Check if callback is really called
     66  * TODO test for BAD response in various places?
     67  * TODO test for BYE response in various places?
     68  */
     69 @SmallTest
     70 public class ImapStoreUnitTests extends AndroidTestCase {
     71     private final static String[] NO_REPLY = new String[0];
     72 
     73     /**
     74      * Default folder name.  In order to test for encoding, we use a non-ascii name.
     75      */
     76     private final static String FOLDER_NAME = "\u65E5";
     77 
     78     /**
     79      * Folder name encoded in UTF-7.
     80      */
     81     private final static String FOLDER_ENCODED = "&ZeU-";
     82 
     83     private static ImapResponse CAPABILITY_RESPONSE = ImapTestUtils.parseResponse(
     84             "* CAPABILITY IMAP4rev1 STARTTLS");
     85 
     86     /* These values are provided by setUp() */
     87     private ImapStore mStore = null;
     88     private ImapStore.ImapFolder mFolder = null;
     89 
     90     private int mNextTag;
     91 
     92     /**
     93      * Setup code.  We generate a lightweight ImapStore and ImapStore.ImapFolder.
     94      */
     95     @Override
     96     protected void setUp() throws Exception {
     97         super.setUp();
     98         Email.setTempDirectory(getContext());
     99 
    100         // These are needed so we can get at the inner classes
    101         mStore = (ImapStore) ImapStore.newInstance("imap://user:password@server:999",
    102                 getContext(), null);
    103         mFolder = (ImapStore.ImapFolder) mStore.getFolder(FOLDER_NAME);
    104         mNextTag = 1;
    105     }
    106 
    107     public void testJoinMessageUids() throws Exception {
    108         assertEquals("", ImapStore.joinMessageUids(new Message[] {}));
    109         assertEquals("a", ImapStore.joinMessageUids(new Message[] {
    110                 mFolder.createMessage("a")
    111                 }));
    112         assertEquals("a,XX", ImapStore.joinMessageUids(new Message[] {
    113                 mFolder.createMessage("a"),
    114                 mFolder.createMessage("XX"),
    115                 }));
    116     }
    117 
    118     /**
    119      * Confirms simple non-SSL non-TLS login
    120      */
    121     public void testSimpleLogin() throws MessagingException {
    122 
    123         MockTransport mockTransport = openAndInjectMockTransport();
    124 
    125         // try to open it
    126         setupOpenFolder(mockTransport);
    127         mFolder.open(OpenMode.READ_WRITE, null);
    128 
    129         // TODO: inject specific facts in the initial folder SELECT and check them here
    130     }
    131 
    132     public void testLoginFailure() throws Exception {
    133         MockTransport mockTransport = openAndInjectMockTransport();
    134         expectLogin(mockTransport, new String[] {"* iD nIL", "oK"}, "nO authentication failed");
    135 
    136         try {
    137             mStore.getConnection().open();
    138             fail("Didn't throw AuthenticationFailedException");
    139         } catch (AuthenticationFailedException expected) {
    140         }
    141     }
    142 
    143     /**
    144      * TODO: Test with SSL negotiation (faked)
    145      * TODO: Test with SSL required but not supported
    146      * TODO: Test with TLS negotiation (faked)
    147      * TODO: Test with TLS required but not supported
    148      */
    149 
    150     /**
    151      * Test the generation of the IMAP ID keys
    152      */
    153     public void testImapIdBasic() {
    154         // First test looks at operation of the outer API - we don't control any of the
    155         // values;  Just look for basic results.
    156 
    157         // Strings we'll expect to find:
    158         //   name            Android package name of the program
    159         //   os              "android"
    160         //   os-version      "version; build-id"
    161         //   vendor          Vendor of the client/server
    162         //   x-android-device-model Model (Optional, so not tested here)
    163         //   x-android-net-operator Carrier (Unreliable, so not tested here)
    164         //   AGUID           A device+account UID
    165         String id = ImapStore.getImapId(getContext(), "user-name", "host-name",
    166                 CAPABILITY_RESPONSE);
    167         HashMap<String, String> map = tokenizeImapId(id);
    168         assertEquals(getContext().getPackageName(), map.get("name"));
    169         assertEquals("android", map.get("os"));
    170         assertNotNull(map.get("os-version"));
    171         assertNotNull(map.get("vendor"));
    172         assertNotNull(map.get("AGUID"));
    173 
    174         // Next, use the inner API to confirm operation of a couple of
    175         // variants for release and non-release devices.
    176 
    177         // simple API check - non-REL codename, non-empty version
    178         id = ImapStore.makeCommonImapId("packageName", "version", "codeName",
    179                 "model", "id", "vendor", "network-operator");
    180         map = tokenizeImapId(id);
    181         assertEquals("packageName", map.get("name"));
    182         assertEquals("android", map.get("os"));
    183         assertEquals("version; id", map.get("os-version"));
    184         assertEquals("vendor", map.get("vendor"));
    185         assertEquals(null, map.get("x-android-device-model"));
    186         assertEquals("network-operator", map.get("x-android-mobile-net-operator"));
    187         assertEquals(null, map.get("AGUID"));
    188 
    189         // simple API check - codename is REL, so use model name.
    190         // also test empty version => 1.0 and empty network operator
    191         id = ImapStore.makeCommonImapId("packageName", "", "REL",
    192                 "model", "id", "vendor", "");
    193         map = tokenizeImapId(id);
    194         assertEquals("packageName", map.get("name"));
    195         assertEquals("android", map.get("os"));
    196         assertEquals("1.0; id", map.get("os-version"));
    197         assertEquals("vendor", map.get("vendor"));
    198         assertEquals("model", map.get("x-android-device-model"));
    199         assertEquals(null, map.get("x-android-mobile-net-operator"));
    200         assertEquals(null, map.get("AGUID"));
    201     }
    202 
    203     /**
    204      * Test for the interaction between {@link ImapStore#getImapId} and a vendor policy.
    205      */
    206     public void testImapIdWithVendorPolicy() {
    207         try {
    208             MockVendorPolicy.inject(getContext());
    209 
    210             // Prepare mock result
    211             Bundle result = new Bundle();
    212             result.putString("getImapId", "\"test-key\" \"test-value\"");
    213             MockVendorPolicy.mockResult = result;
    214 
    215             // Invoke
    216             String id = ImapStore.getImapId(getContext(), "user-name", "host-name",
    217                     ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z"));
    218 
    219             // Check the result
    220             assertEquals("test-value", tokenizeImapId(id).get("test-key"));
    221 
    222             // Verify what's passed to the policy
    223             assertEquals("getImapId", MockVendorPolicy.passedPolicy);
    224             assertEquals("user-name", MockVendorPolicy.passedBundle.getString("getImapId.user"));
    225             assertEquals("host-name", MockVendorPolicy.passedBundle.getString("getImapId.host"));
    226             assertEquals("[CAPABILITY,IMAP4rev1,XXX,YYY,Z]",
    227                     MockVendorPolicy.passedBundle.getString("getImapId.capabilities"));
    228         } finally {
    229             VendorPolicyLoader.clearInstanceForTest();
    230         }
    231     }
    232 
    233     /**
    234      * Test of the internal generator for IMAP ID strings, specifically looking for proper
    235      * filtering of illegal values.  This is required because we cannot necessarily trust
    236      * the external sources of some of this data (e.g. release labels).
    237      *
    238      * The (somewhat arbitrary) legal values are:  a-z A-Z 0-9 - _ + = ; : . , / <space>
    239      * The most important goal of the filters is to keep out control chars, (, ), and "
    240      */
    241     public void testImapIdFiltering() {
    242         String id = ImapStore.makeCommonImapId(
    243                 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    244                 "0123456789", "codeName",
    245                 "model", "-_+=;:.,// ",
    246                 "v(e)n\"d\ro\nr",           // look for bad chars stripped out, leaving OK chars
    247                 "()\"");                    // look for bad chars stripped out, leaving nothing
    248         HashMap<String, String> map = tokenizeImapId(id);
    249 
    250         assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name"));
    251         assertEquals("0123456789; -_+=;:.,// ", map.get("os-version"));
    252         assertEquals("vendor", map.get("vendor"));
    253         assertNull(map.get("x-android-mobile-net-operator"));
    254     }
    255 
    256     /**
    257      * Test that IMAP ID uid's are per-username
    258      */
    259     public void testImapIdDeviceId() throws MessagingException {
    260         ImapStore store1a = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
    261                 getContext(), null);
    262         ImapStore store1b = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
    263                 getContext(), null);
    264         ImapStore store2 = (ImapStore) ImapStore.newInstance("imap://user2:password@server:999",
    265                 getContext(), null);
    266 
    267         String id1a = ImapStore.getImapId(getContext(), "user1", "host-name", CAPABILITY_RESPONSE);
    268         String id1b = ImapStore.getImapId(getContext(), "user1", "host-name", CAPABILITY_RESPONSE);
    269         String id2 = ImapStore.getImapId(getContext(), "user2", "host-name", CAPABILITY_RESPONSE);
    270 
    271         String uid1a = tokenizeImapId(id1a).get("AGUID");
    272         String uid1b = tokenizeImapId(id1b).get("AGUID");
    273         String uid2 = tokenizeImapId(id2).get("AGUID");
    274 
    275         assertEquals(uid1a, uid1b);
    276         MoreAsserts.assertNotEqual(uid1a, uid2);
    277     }
    278 
    279     /**
    280      * Helper to break an IMAP ID string into keys & values
    281      * @param id the IMAP Id string (the part inside the parens)
    282      * @return a map of key/value pairs
    283      */
    284     private HashMap<String, String> tokenizeImapId(String id) {
    285         // Instead of a true tokenizer, we'll use double-quote as the split.
    286         // We can's use " " because there may be spaces inside the values.
    287         String[] elements = id.split("\"");
    288         HashMap<String, String> map = new HashMap<String, String>();
    289         for (int i = 0; i < elements.length; ) {
    290             // Because we split at quotes, we expect to find:
    291             // [i] = null or one or more spaces
    292             // [i+1] = key
    293             // [i+2] = one or more spaces
    294             // [i+3] = value
    295             map.put(elements[i+1], elements[i+3]);
    296             i += 4;
    297         }
    298         return map;
    299     }
    300 
    301     /**
    302      * Test non-NIL server response to IMAP ID.  We should simply ignore it.
    303      */
    304     public void testServerId() throws MessagingException {
    305         MockTransport mockTransport = openAndInjectMockTransport();
    306 
    307         // try to open it
    308         setupOpenFolder(mockTransport, new String[] {
    309                 "* ID (\"name\" \"Cyrus\" \"version\" \"1.5\"" +
    310                 " \"os\" \"sunos\" \"os-version\" \"5.5\"" +
    311                 " \"support-url\" \"mailto:cyrus-bugs+@andrew.cmu.edu\")",
    312                 "oK"}, "rEAD-wRITE");
    313         mFolder.open(OpenMode.READ_WRITE, null);
    314     }
    315 
    316     /**
    317      * Test OK response to IMAP ID with crummy text afterwards too.
    318      */
    319     public void testImapIdOkParsing() throws MessagingException {
    320         MockTransport mockTransport = openAndInjectMockTransport();
    321 
    322         // try to open it
    323         setupOpenFolder(mockTransport, new String[] {
    324                 "* iD nIL",
    325                 "oK [iD] bad-char-%"}, "rEAD-wRITE");
    326         mFolder.open(OpenMode.READ_WRITE, null);
    327     }
    328 
    329     /**
    330      * Test BAD response to IMAP ID - also with bad parser chars
    331      */
    332     public void testImapIdBad() throws MessagingException {
    333         MockTransport mockTransport = openAndInjectMockTransport();
    334 
    335         // try to open it
    336         setupOpenFolder(mockTransport, new String[] {
    337                 "bAD unknown command bad-char-%"}, "rEAD-wRITE");
    338         mFolder.open(OpenMode.READ_WRITE, null);
    339     }
    340 
    341     /**
    342      * Test small Folder functions that don't really do anything in Imap
    343      */
    344     public void testSmallFolderFunctions() throws MessagingException {
    345         // getPermanentFlags() returns { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }
    346         Flag[] flags = mFolder.getPermanentFlags();
    347         assertEquals(3, flags.length);
    348         // TODO: Write flags into hashset and compare them to a hashset and compare them
    349         assertEquals(Flag.DELETED, flags[0]);
    350         assertEquals(Flag.SEEN, flags[1]);
    351         assertEquals(Flag.FLAGGED, flags[2]);
    352 
    353         // canCreate() returns true
    354         assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
    355         assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
    356     }
    357 
    358     /**
    359      * Lightweight test to confirm that IMAP hasn't implemented any folder roles yet.
    360      *
    361      * TODO: Test this with multiple folders provided by mock server
    362      * TODO: Implement XLIST and then support this
    363      */
    364     public void testNoFolderRolesYet() {
    365         assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole());
    366     }
    367 
    368     /**
    369      * Lightweight test to confirm that IMAP isn't requesting structure prefetch.
    370      */
    371     public void testNoStructurePrefetch() {
    372         assertFalse(mStore.requireStructurePrefetch());
    373     }
    374 
    375     /**
    376      * Lightweight test to confirm that IMAP is requesting sent-message-upload.
    377      * TODO: Implement Gmail-specific cases and handle this server-side
    378      */
    379     public void testSentUploadRequested() {
    380         assertTrue(mStore.requireCopyMessageToSentFolder());
    381     }
    382 
    383     /**
    384      * TODO: Test the process of opening and indexing a mailbox with one unread message in it.
    385      */
    386 
    387     /**
    388      * TODO: Test the scenario where the transport is "open" but not really (e.g. server closed).
    389     /**
    390      * Set up a basic MockTransport. open it, and inject it into mStore
    391      */
    392     private MockTransport openAndInjectMockTransport() {
    393         // Create mock transport and inject it into the ImapStore that's already set up
    394         MockTransport mockTransport = new MockTransport();
    395         mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
    396         mockTransport.setMockHost("mock.server.com");
    397         mStore.setTransport(mockTransport);
    398         return mockTransport;
    399     }
    400 
    401     /**
    402      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    403      *
    404      * @param mockTransport the mock transport we're using
    405      */
    406     private void setupOpenFolder(MockTransport mockTransport) {
    407         setupOpenFolder(mockTransport, "rEAD-wRITE");
    408     }
    409 
    410     /**
    411      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    412      *
    413      * @param mockTransport the mock transport we're using
    414      */
    415     private void setupOpenFolder(MockTransport mockTransport, String readWriteMode) {
    416         setupOpenFolder(mockTransport, new String[] {
    417                 "* iD nIL", "oK"}, readWriteMode);
    418     }
    419 
    420     /**
    421      * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
    422      * Also allows setting a custom IMAP ID.
    423      *
    424      * Also sets mNextTag, an int, which is useful if there are additional commands to inject.
    425      *
    426      * @param mockTransport the mock transport we're using
    427      * @param imapIdResponse the expected series of responses to the IMAP ID command.  Non-final
    428      *      lines should be tagged with *.  The final response should be untagged (the correct
    429      *      tag will be added at runtime).
    430      * @param "READ-WRITE" or "READ-ONLY"
    431      * @return the next tag# to use
    432      */
    433     private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
    434             String readWriteMode) {
    435         expectLogin(mockTransport, imapIdResponse);
    436         mockTransport.expect(
    437                 getNextTag(false) + " SELECT \"" + FOLDER_ENCODED + "\"", new String[] {
    438                 "* fLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
    439                 "* oK [pERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
    440                 "* 0 eXISTS",
    441                 "* 0 rECENT",
    442                 "* OK [uNSEEN 0]",
    443                 "* OK [uIDNEXT 1]",
    444                 getNextTag(true) + " oK [" + readWriteMode + "] " +
    445                         FOLDER_ENCODED + " selected. (Success)"});
    446     }
    447 
    448     private void expectLogin(MockTransport mockTransport) {
    449         expectLogin(mockTransport, new String[] {"* iD nIL", "oK"});
    450     }
    451 
    452     private void expectLogin(MockTransport mockTransport, String[] imapIdResponse) {
    453         expectLogin(mockTransport, imapIdResponse, "oK user authenticated (Success)");
    454     }
    455 
    456     private void expectLogin(MockTransport mockTransport, String[] imapIdResponse,
    457             String loginResponse) {
    458         // inject boilerplate commands that match our typical login
    459         mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You");
    460 
    461         mockTransport.expect(getNextTag(false) + " CAPABILITY", new String[] {
    462                 "* cAPABILITY iMAP4rev1 sTARTTLS aUTH=gSSAPI lOGINDISABLED",
    463                 getNextTag(true) + " oK CAPABILITY completed"});
    464 
    465         String expectedNextTag = getNextTag(false);
    466         // Fix the tag # of the ID response
    467         String last = imapIdResponse[imapIdResponse.length-1];
    468         last = expectedNextTag + " " + last;
    469         imapIdResponse[imapIdResponse.length-1] = last;
    470         mockTransport.expect(getNextTag(false) + " ID \\(.*\\)", imapIdResponse);
    471 
    472         getNextTag(true); // Advance the tag for ID response.
    473 
    474         mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"",
    475                 getNextTag(true) + " " + loginResponse);
    476     }
    477 
    478     private void expectNoop(MockTransport mockTransport, boolean ok) {
    479         String response = ok ? " oK success" : " nO timeout";
    480         mockTransport.expect(getNextTag(false) + " NOOP",
    481                 new String[] {getNextTag(true) + response});
    482     }
    483 
    484     /**
    485      * Return a tag for use in setting up expect strings.  Typically this is called in pairs,
    486      * first as getNextTag(false) when emitting the command, then as getNextTag(true) when
    487      * emitting the final line of the expected response.
    488      * @param advance true to increment mNextTag for the subsequence command
    489      * @return a string containing the current tag
    490      */
    491     public String getNextTag(boolean advance)  {
    492         if (advance) ++mNextTag;
    493         return Integer.toString(mNextTag);
    494     }
    495 
    496     /**
    497      * Test that servers reporting READ-WRITE mode are parsed properly
    498      * Note: the READ_WRITE mode passed to folder.open() does not affect the test
    499      */
    500     public void testReadWrite() throws MessagingException {
    501         MockTransport mock = openAndInjectMockTransport();
    502         setupOpenFolder(mock, "rEAD-WRITE");
    503         mFolder.open(OpenMode.READ_WRITE, null);
    504         assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
    505     }
    506 
    507     /**
    508      * Test that servers reporting READ-ONLY mode are parsed properly
    509      * Note: the READ_ONLY mode passed to folder.open() does not affect the test
    510      */
    511     public void testReadOnly() throws MessagingException {
    512         MockTransport mock = openAndInjectMockTransport();
    513         setupOpenFolder(mock, "rEAD-ONLY");
    514         mFolder.open(OpenMode.READ_ONLY, null);
    515         assertEquals(OpenMode.READ_ONLY, mFolder.getMode());
    516     }
    517 
    518     /**
    519      * Test for getUnreadMessageCount with quoted string in the middle of response.
    520      */
    521     public void testGetUnreadMessageCountWithQuotedString() throws Exception {
    522         MockTransport mock = openAndInjectMockTransport();
    523         setupOpenFolder(mock);
    524         mock.expect(
    525                 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
    526                 new String[] {
    527                 "* sTATUS \"" + FOLDER_ENCODED + "\" (uNSEEN 2)",
    528                 getNextTag(true) + " oK STATUS completed"});
    529         mFolder.open(OpenMode.READ_WRITE, null);
    530         int unreadCount = mFolder.getUnreadMessageCount();
    531         assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount);
    532     }
    533 
    534     /**
    535      * Test for getUnreadMessageCount with literal string in the middle of response.
    536      */
    537     public void testGetUnreadMessageCountWithLiteralString() throws Exception {
    538         MockTransport mock = openAndInjectMockTransport();
    539         setupOpenFolder(mock);
    540         mock.expect(
    541                 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
    542                 new String[] {
    543                 "* sTATUS {5}",
    544                 FOLDER_ENCODED + " (uNSEEN 10)",
    545                 getNextTag(true) + " oK STATUS completed"});
    546         mFolder.open(OpenMode.READ_WRITE, null);
    547         int unreadCount = mFolder.getUnreadMessageCount();
    548         assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
    549     }
    550 
    551     public void testFetchFlagEnvelope() throws MessagingException {
    552         final MockTransport mock = openAndInjectMockTransport();
    553         setupOpenFolder(mock);
    554         mFolder.open(OpenMode.READ_WRITE, null);
    555         final Message message = mFolder.createMessage("1");
    556 
    557         final FetchProfile fp = new FetchProfile();
    558         fp.add(FetchProfile.Item.FLAGS);
    559         fp.add(FetchProfile.Item.ENVELOPE);
    560         mock.expect(getNextTag(false) +
    561                 " UID FETCH 1 \\(UID FLAGS INTERNALDATE RFC822\\.SIZE BODY\\.PEEK\\[HEADER.FIELDS" +
    562                         " \\(date subject from content-type to cc message-id\\)\\]\\)",
    563                 new String[] {
    564                 "* 9 fETCH (uID 1 rFC822.sIZE 120626 iNTERNALDATE \"17-may-2010 22:00:15 +0000\"" +
    565                         "fLAGS (\\Seen) bODY[hEADER.FIELDS (dAte sUbject fRom cOntent-type tO cC" +
    566                         " mEssage-id)]" +
    567                         " {279}",
    568                 "From: Xxxxxx Yyyyy <userxx (at) android.com>",
    569                 "Date: Mon, 17 May 2010 14:59:52 -0700",
    570                 "Message-ID: <x0000000000000000000000000000000000000000000000y (at) android.com>",
    571                 "Subject: ssubject",
    572                 "To: android.test01 (at) android.com",
    573                 "Content-Type: multipart/mixed; boundary=a00000000000000000000000000b",
    574                 "",
    575                 ")",
    576                 getNextTag(true) + " oK SUCCESS"
    577         });
    578         mFolder.fetch(new Message[] { message }, fp, null);
    579 
    580         assertEquals("android.test01 (at) android.com", message.getHeader("to")[0]);
    581         assertEquals("Xxxxxx Yyyyy <userxx (at) android.com>", message.getHeader("from")[0]);
    582         assertEquals("multipart/mixed; boundary=a00000000000000000000000000b",
    583                 message.getHeader("Content-Type")[0]);
    584         assertTrue(message.isSet(Flag.SEEN));
    585 
    586         // TODO: Test NO response.
    587     }
    588 
    589     /**
    590      * Test for fetching simple BODYSTRUCTURE.
    591      */
    592     public void testFetchBodyStructureSimple() throws Exception {
    593         final MockTransport mock = openAndInjectMockTransport();
    594         setupOpenFolder(mock);
    595         mFolder.open(OpenMode.READ_WRITE, null);
    596         final Message message = mFolder.createMessage("1");
    597 
    598         final FetchProfile fp = new FetchProfile();
    599         fp.add(FetchProfile.Item.STRUCTURE);
    600         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
    601                 new String[] {
    602                 "* 9 fETCH (uID 1 bODYSTRUCTURE (\"tEXT\" \"pLAIN\" nIL" +
    603                         " nIL nIL nIL 18 3 nIL nIL nIL))",
    604                 getNextTag(true) + " oK sUCCESS"
    605         });
    606         mFolder.fetch(new Message[] { message }, fp, null);
    607 
    608         // Check mime structure...
    609         MoreAsserts.assertEquals(
    610                 new String[] {"text/plain"},
    611                 message.getHeader("Content-Type")
    612                 );
    613         assertNull(message.getHeader("Content-Transfer-Encoding"));
    614         assertNull(message.getHeader("Content-ID"));
    615         MoreAsserts.assertEquals(
    616                 new String[] {";\n size=18"},
    617                 message.getHeader("Content-Disposition")
    618                 );
    619 
    620         MoreAsserts.assertEquals(
    621                 new String[] {"TEXT"},
    622                 message.getHeader("X-Android-Attachment-StoreData")
    623                 );
    624 
    625         // TODO: Test NO response.
    626     }
    627 
    628     /**
    629      * Test for fetching complex muiltipart BODYSTRUCTURE.
    630      */
    631     public void testFetchBodyStructureMultipart() throws Exception {
    632         final MockTransport mock = openAndInjectMockTransport();
    633         setupOpenFolder(mock);
    634         mFolder.open(OpenMode.READ_WRITE, null);
    635         final Message message = mFolder.createMessage("1");
    636 
    637         final FetchProfile fp = new FetchProfile();
    638         fp.add(FetchProfile.Item.STRUCTURE);
    639         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
    640                 new String[] {
    641                 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"pLAIN\" () {20}",
    642                 "long content id#@!@#" +
    643                     " NIL \"7BIT\" 18 3 NIL NIL NIL)" +
    644                     "(\"IMAGE\" \"PNG\" (\"NAME\" {10}",
    645                 "device.png) NIL NIL \"BASE64\" {6}",
    646                 "117840 NIL (\"aTTACHMENT\" (\"fILENAME\" \"device.png\")) NIL)" +
    647                     "(\"TEXT\" \"HTML\" () NIL NIL \"7BIT\" 100 NIL 123 (\"aTTACHMENT\"" +
    648                     "(\"fILENAME\" {15}",
    649                 "attachment.html \"SIZE\" 555)) NIL)" +
    650                     "((\"TEXT\" \"HTML\" NIL NIL \"BASE64\")(\"XXX\" \"YYY\"))" + // Nested
    651                     "\"mIXED\" (\"bOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))",
    652                 getNextTag(true) + " oK SUCCESS"
    653         });
    654         mFolder.fetch(new Message[] { message }, fp, null);
    655 
    656         // Check mime structure...
    657         final Body body = message.getBody();
    658         assertTrue(body instanceof MimeMultipart);
    659         MimeMultipart mimeMultipart = (MimeMultipart) body;
    660         assertEquals(4, mimeMultipart.getCount());
    661         assertEquals("mixed", mimeMultipart.getSubTypeForTest());
    662 
    663         final Part part1 = mimeMultipart.getBodyPart(0);
    664         final Part part2 = mimeMultipart.getBodyPart(1);
    665         final Part part3 = mimeMultipart.getBodyPart(2);
    666         final Part part4 = mimeMultipart.getBodyPart(3);
    667         assertTrue(part1 instanceof MimeBodyPart);
    668         assertTrue(part2 instanceof MimeBodyPart);
    669         assertTrue(part3 instanceof MimeBodyPart);
    670         assertTrue(part4 instanceof MimeBodyPart);
    671 
    672         final MimeBodyPart mimePart1 = (MimeBodyPart) part1; // text/plain
    673         final MimeBodyPart mimePart2 = (MimeBodyPart) part2; // image/png
    674         final MimeBodyPart mimePart3 = (MimeBodyPart) part3; // text/html
    675         final MimeBodyPart mimePart4 = (MimeBodyPart) part4; // Nested
    676 
    677         MoreAsserts.assertEquals(
    678                 new String[] {"1"},
    679                 part1.getHeader("X-Android-Attachment-StoreData")
    680                 );
    681         MoreAsserts.assertEquals(
    682                 new String[] {"2"},
    683                 part2.getHeader("X-Android-Attachment-StoreData")
    684                 );
    685         MoreAsserts.assertEquals(
    686                 new String[] {"3"},
    687                 part3.getHeader("X-Android-Attachment-StoreData")
    688                 );
    689 
    690         MoreAsserts.assertEquals(
    691                 new String[] {"text/plain"},
    692                 part1.getHeader("Content-Type")
    693                 );
    694         MoreAsserts.assertEquals(
    695                 new String[] {"image/png;\n NAME=\"device.png\""},
    696                 part2.getHeader("Content-Type")
    697                 );
    698         MoreAsserts.assertEquals(
    699                 new String[] {"text/html"},
    700                 part3.getHeader("Content-Type")
    701                 );
    702 
    703         MoreAsserts.assertEquals(
    704                 new String[] {"long content id#@!@#"},
    705                 part1.getHeader("Content-ID")
    706                 );
    707         assertNull(part2.getHeader("Content-ID"));
    708         assertNull(part3.getHeader("Content-ID"));
    709 
    710         MoreAsserts.assertEquals(
    711                 new String[] {"7BIT"},
    712                 part1.getHeader("Content-Transfer-Encoding")
    713                 );
    714         MoreAsserts.assertEquals(
    715                 new String[] {"BASE64"},
    716                 part2.getHeader("Content-Transfer-Encoding")
    717                 );
    718         MoreAsserts.assertEquals(
    719                 new String[] {"7BIT"},
    720                 part3.getHeader("Content-Transfer-Encoding")
    721                 );
    722 
    723         MoreAsserts.assertEquals(
    724                 new String[] {";\n size=18"},
    725                 part1.getHeader("Content-Disposition")
    726                 );
    727         MoreAsserts.assertEquals(
    728                 new String[] {"attachment;\n filename=\"device.png\";\n size=117840"},
    729                 part2.getHeader("Content-Disposition")
    730                 );
    731         MoreAsserts.assertEquals(
    732                 new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""},
    733                 part3.getHeader("Content-Disposition")
    734                 );
    735 
    736         // Check the nested parts.
    737         final Body part4body = part4.getBody();
    738         assertTrue(part4body instanceof MimeMultipart);
    739         MimeMultipart mimeMultipartPart4 = (MimeMultipart) part4body;
    740         assertEquals(2, mimeMultipartPart4.getCount());
    741 
    742         final MimeBodyPart mimePart41 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(0);
    743         final MimeBodyPart mimePart42 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(1);
    744 
    745         MoreAsserts.assertEquals(new String[] {"4.1"},
    746                 mimePart41.getHeader("X-Android-Attachment-StoreData")
    747                 );
    748         MoreAsserts.assertEquals(new String[] {"4.2"},
    749                 mimePart42.getHeader("X-Android-Attachment-StoreData")
    750                 );
    751     }
    752 
    753     public void testFetchBodySane() throws MessagingException {
    754         final MockTransport mock = openAndInjectMockTransport();
    755         setupOpenFolder(mock);
    756         mFolder.open(OpenMode.READ_WRITE, null);
    757         final Message message = mFolder.createMessage("1");
    758 
    759         final FetchProfile fp = new FetchProfile();
    760         fp.add(FetchProfile.Item.BODY_SANE);
    761         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]<0.51200>\\)",
    762                 new String[] {
    763                 "* 9 fETCH (uID 1 bODY[] {23}",
    764                 "from: a (at) b.com", // 15 bytes
    765                 "", // 2
    766                 "test", // 6
    767                 ")",
    768                 getNextTag(true) + " oK SUCCESS"
    769         });
    770         mFolder.fetch(new Message[] { message }, fp, null);
    771         assertEquals("a (at) b.com", message.getHeader("from")[0]);
    772 
    773         // TODO: Test NO response.
    774     }
    775 
    776     public void testFetchBody() throws MessagingException {
    777         final MockTransport mock = openAndInjectMockTransport();
    778         setupOpenFolder(mock);
    779         mFolder.open(OpenMode.READ_WRITE, null);
    780         final Message message = mFolder.createMessage("1");
    781 
    782         final FetchProfile fp = new FetchProfile();
    783         fp.add(FetchProfile.Item.BODY);
    784         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]\\)",
    785                 new String[] {
    786                 "* 9 fETCH (uID 1 bODY[] {23}",
    787                 "from: a (at) b.com", // 15 bytes
    788                 "", // 2
    789                 "test", // 6
    790                 ")",
    791                 getNextTag(true) + " oK SUCCESS"
    792         });
    793         mFolder.fetch(new Message[] { message }, fp, null);
    794         assertEquals("a (at) b.com", message.getHeader("from")[0]);
    795 
    796         // TODO: Test NO response.
    797     }
    798 
    799     public void testFetchAttachment() throws Exception {
    800         MockTransport mock = openAndInjectMockTransport();
    801         setupOpenFolder(mock);
    802         mFolder.open(OpenMode.READ_WRITE, null);
    803         final Message message = mFolder.createMessage("1");
    804 
    805         final FetchProfile fp = new FetchProfile();
    806         fp.add(FetchProfile.Item.STRUCTURE);
    807         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
    808                 new String[] {
    809                 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"PLAIN\" (\"cHARSET\" \"iSO-8859-1\")" +
    810                         " CID nIL \"7bIT\" 18 3 NIL NIL NIL)" +
    811                         "(\"IMAGE\" \"PNG\"" +
    812                         " (\"nAME\" \"device.png\") NIL NIL \"bASE64\" 117840 NIL (\"aTTACHMENT\"" +
    813                         "(\"fILENAME\" \"device.png\")) NIL)" +
    814                         "\"mIXED\"))",
    815                 getNextTag(true) + " OK SUCCESS"
    816         });
    817         mFolder.fetch(new Message[] { message }, fp, null);
    818 
    819         // Check mime structure, and get the second part.
    820         Body body = message.getBody();
    821         assertTrue(body instanceof MimeMultipart);
    822         MimeMultipart mimeMultipart = (MimeMultipart) body;
    823         assertEquals(2, mimeMultipart.getCount());
    824 
    825         Part part1 = mimeMultipart.getBodyPart(1);
    826         assertTrue(part1 instanceof MimeBodyPart);
    827         MimeBodyPart mimePart1 = (MimeBodyPart) part1;
    828 
    829         // Fetch the second part
    830         fp.clear();
    831         fp.add(mimePart1);
    832         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[2\\]\\)",
    833                 new String[] {
    834                 "* 9 fETCH (uID 1 bODY[2] {4}",
    835                 "YWJj)", // abc in base64
    836                 getNextTag(true) + " oK SUCCESS"
    837         });
    838         mFolder.fetch(new Message[] { message }, fp, null);
    839 
    840         assertEquals("abc",
    841                 Utility.fromUtf8(IOUtils.toByteArray(mimePart1.getBody().getInputStream())));
    842 
    843         // TODO: Test NO response.
    844     }
    845 
    846     /**
    847      * Test for proper operations on servers that return "NIL" for empty message bodies.
    848      */
    849     public void testNilMessage() throws MessagingException {
    850         MockTransport mock = openAndInjectMockTransport();
    851         setupOpenFolder(mock);
    852         mFolder.open(OpenMode.READ_WRITE, null);
    853 
    854         // Prepare to pull structure and peek body text - this is like the "large message"
    855         // loop in MessagingController.synchronizeMailboxGeneric()
    856         FetchProfile fp = new FetchProfile();fp.clear();
    857         fp.add(FetchProfile.Item.STRUCTURE);
    858         Message message1 = mFolder.createMessage("1");
    859         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
    860                 "* 1 fETCH (uID 1 bODYSTRUCTURE (tEXT pLAIN nIL nIL nIL 7bIT 0 0 nIL nIL nIL))",
    861                 getNextTag(true) + " oK SUCCESS"
    862         });
    863         mFolder.fetch(new Message[] { message1 }, fp, null);
    864 
    865         // The expected result for an empty body is:
    866         //   * 1 FETCH (UID 1 BODY[TEXT] {0})
    867         // But some servers are returning NIL for the empty body:
    868         //   * 1 FETCH (UID 1 BODY[TEXT] NIL)
    869         // Because this breaks our little parser, fetch() skips over empty parts.
    870         // The rest of this test is confirming that this is the case.
    871 
    872         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
    873                 "* 1 fETCH (uID 1 bODY[tEXT] nIL)",
    874                 getNextTag(true) + " oK SUCCESS"
    875         });
    876         ArrayList<Part> viewables = new ArrayList<Part>();
    877         ArrayList<Part> attachments = new ArrayList<Part>();
    878         MimeUtility.collectParts(message1, viewables, attachments);
    879         assertTrue(viewables.size() == 1);
    880         Part emptyBodyPart = viewables.get(0);
    881         fp.clear();
    882         fp.add(emptyBodyPart);
    883         mFolder.fetch(new Message[] { message1 }, fp, null);
    884 
    885         // If this wasn't working properly, there would be an attempted interpretation
    886         // of the empty part's NIL and possibly a crash.
    887 
    888         // If this worked properly, the "empty" body can now be retrieved
    889         viewables = new ArrayList<Part>();
    890         attachments = new ArrayList<Part>();
    891         MimeUtility.collectParts(message1, viewables, attachments);
    892         assertTrue(viewables.size() == 1);
    893         emptyBodyPart = viewables.get(0);
    894         String text = MimeUtility.getTextFromPart(emptyBodyPart);
    895         assertNull(text);
    896     }
    897 
    898     /**
    899      * Confirm the IMAP parser won't crash when seeing an excess FETCH response line without UID.
    900      *
    901      * <p>We've observed that the secure.emailsrvr.com email server returns an excess FETCH response
    902      * for a UID FETCH command.  These excess responses doesn't have the UID field in it, even
    903      * though we request, which led the response parser to crash.  We fixed it by ignoring response
    904      * lines that don't have UID.  This test is to make sure this case.
    905      */
    906     public void testExcessFetchResult() throws MessagingException {
    907         MockTransport mock = openAndInjectMockTransport();
    908         setupOpenFolder(mock);
    909         mFolder.open(OpenMode.READ_WRITE, null);
    910 
    911         // Create a message, and make sure it's not "SEEN".
    912         Message message1 = mFolder.createMessage("1");
    913         assertFalse(message1.isSet(Flag.SEEN));
    914 
    915         FetchProfile fp = new FetchProfile();
    916         fp.clear();
    917         fp.add(FetchProfile.Item.FLAGS);
    918         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID FLAGS\\)",
    919                 new String[] {
    920                 "* 1 fETCH (uID 1 fLAGS (\\Seen))",
    921                 "* 2 fETCH (fLAGS (\\Seen))",
    922                 getNextTag(true) + " oK SUCCESS"
    923         });
    924 
    925         // Shouldn't crash
    926         mFolder.fetch(new Message[] { message1 }, fp, null);
    927 
    928         // And the message is "SEEN".
    929         assertTrue(message1.isSet(Flag.SEEN));
    930     }
    931 
    932 
    933     private ImapMessage prepareForAppendTest(MockTransport mock, String response) throws Exception {
    934         ImapMessage message = (ImapMessage) mFolder.createMessage("initial uid");
    935         message.setFrom(new Address("me (at) test.com"));
    936         message.setRecipient(RecipientType.TO, new Address("you (at) test.com"));
    937         message.setMessageId("<message.id (at) test.com>");
    938         message.setFlagDirectlyForTest(Flag.SEEN, true);
    939         message.setBody(new TextBody("Test Body"));
    940 
    941         // + go ahead
    942         // * 12345 EXISTS
    943         // OK [APPENDUID 627684530 17] (Success)
    944 
    945         mock.expect(getNextTag(false) +
    946                 " APPEND \\\"" + FOLDER_ENCODED + "\\\" \\(\\\\SEEN\\) \\{166\\}",
    947                 new String[] {"+ gO aHead"});
    948 
    949         mock.expectLiterally("From: me (at) test.com", NO_REPLY);
    950         mock.expectLiterally("To: you (at) test.com", NO_REPLY);
    951         mock.expectLiterally("Message-ID: <message.id (at) test.com>", NO_REPLY);
    952         mock.expectLiterally("Content-Type: text/plain;", NO_REPLY);
    953         mock.expectLiterally(" charset=utf-8", NO_REPLY);
    954         mock.expectLiterally("Content-Transfer-Encoding: base64", NO_REPLY);
    955         mock.expectLiterally("", NO_REPLY);
    956         mock.expectLiterally("VGVzdCBCb2R5", NO_REPLY);
    957         mock.expectLiterally("", new String[] {
    958                 "* 7 eXISTS",
    959                 getNextTag(true) + " " + response
    960                 });
    961         return message;
    962     }
    963 
    964     /**
    965      * Test for APPEND when the response has APPENDUID.
    966      */
    967     public void testAppendMessages() throws Exception {
    968         MockTransport mock = openAndInjectMockTransport();
    969         setupOpenFolder(mock);
    970         mFolder.open(OpenMode.READ_WRITE, null);
    971 
    972         ImapMessage message = prepareForAppendTest(mock, "oK [aPPENDUID 1234567 13] (Success)");
    973 
    974         mFolder.appendMessages(new Message[] {message});
    975 
    976         assertEquals("13", message.getUid());
    977         assertEquals(7, mFolder.getMessageCount());
    978     }
    979 
    980     /**
    981      * Test for APPEND when the response doesn't have APPENDUID.
    982      */
    983     public void testAppendMessagesNoAppendUid() throws Exception {
    984         MockTransport mock = openAndInjectMockTransport();
    985         setupOpenFolder(mock);
    986         mFolder.open(OpenMode.READ_WRITE, null);
    987 
    988         ImapMessage message = prepareForAppendTest(mock, "OK Success");
    989 
    990         mock.expectLiterally(
    991                 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id (at) test.com>)",
    992                 new String[] {
    993                 "* sEARCH 321",
    994                 getNextTag(true) + " oK success"
    995                 });
    996 
    997         mFolder.appendMessages(new Message[] {message});
    998 
    999         assertEquals("321", message.getUid());
   1000     }
   1001 
   1002     /**
   1003      * Test for append failure.
   1004      *
   1005      * We don't check the response for APPEND.  We just SEARCH for the message-id to get the UID.
   1006      * If append has failed, the SEARCH command returns no UID, and the UID of the message is left
   1007      * unset.
   1008      */
   1009     public void testAppendFailure() throws Exception {
   1010         MockTransport mock = openAndInjectMockTransport();
   1011         setupOpenFolder(mock);
   1012         mFolder.open(OpenMode.READ_WRITE, null);
   1013 
   1014         ImapMessage message = prepareForAppendTest(mock, "NO No space left on the server.");
   1015         assertEquals("initial uid", message.getUid());
   1016 
   1017         mock.expectLiterally(
   1018                 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id (at) test.com>)",
   1019                 new String[] {
   1020                 "* sEARCH", // not found
   1021                 getNextTag(true) + " oK Search completed."
   1022                 });
   1023 
   1024         mFolder.appendMessages(new Message[] {message});
   1025 
   1026         // Shouldn't have changed
   1027         assertEquals("initial uid", message.getUid());
   1028     }
   1029 
   1030     public void testGetPersonalNamespaces() throws Exception {
   1031         MockTransport mock = openAndInjectMockTransport();
   1032         expectLogin(mock);
   1033 
   1034         mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
   1035                 new String[] {
   1036                 "* lIST (\\HAsNoChildren) \"/\" \"inbox\"",
   1037                 "* lIST (\\hAsnochildren) \"/\" \"Drafts\"",
   1038                 "* lIST (\\nOselect) \"/\" \"no select\"",
   1039                 "* lIST (\\HAsNoChildren) \"/\" \"&ZeVnLIqe-\"", // Japanese folder name
   1040                 getNextTag(true) + " oK SUCCESS"
   1041                 });
   1042         Folder[] folders = mStore.getPersonalNamespaces();
   1043 
   1044         ArrayList<String> list = new ArrayList<String>();
   1045         for (Folder f : folders) {
   1046             list.add(f.getName());
   1047         }
   1048         MoreAsserts.assertEquals(
   1049                 new String[] {"Drafts", "\u65E5\u672C\u8A9E", "INBOX"},
   1050                 list.toArray(new String[0])
   1051                 );
   1052 
   1053         // TODO test with path prefix
   1054         // TODO: Test NO response.
   1055     }
   1056 
   1057     public void testEncodeFolderName() {
   1058         assertEquals("", ImapStore.encodeFolderName(""));
   1059         assertEquals("a", ImapStore.encodeFolderName("a"));
   1060         assertEquals("XYZ", ImapStore.encodeFolderName("XYZ"));
   1061         assertEquals("&ZeVnLIqe-", ImapStore.encodeFolderName("\u65E5\u672C\u8A9E"));
   1062         assertEquals("!&ZeVnLIqe-!", ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!"));
   1063     }
   1064 
   1065     public void testDecodeFolderName() {
   1066         assertEquals("", ImapStore.decodeFolderName(""));
   1067         assertEquals("a", ImapStore.decodeFolderName("a"));
   1068         assertEquals("XYZ", ImapStore.decodeFolderName("XYZ"));
   1069         assertEquals("\u65E5\u672C\u8A9E", ImapStore.decodeFolderName("&ZeVnLIqe-"));
   1070         assertEquals("!\u65E5\u672C\u8A9E!", ImapStore.decodeFolderName("!&ZeVnLIqe-!"));
   1071     }
   1072 
   1073     public void testOpen() throws Exception {
   1074         MockTransport mock = openAndInjectMockTransport();
   1075         expectLogin(mock);
   1076 
   1077         final Folder folder = mStore.getFolder("test");
   1078 
   1079         // Not exist
   1080         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1081                 new String[] {
   1082                 getNextTag(true) + " nO no such mailbox"
   1083                 });
   1084         try {
   1085             folder.open(OpenMode.READ_WRITE, null);
   1086             fail();
   1087         } catch (MessagingException expected) {
   1088         }
   1089 
   1090         // READ-WRITE
   1091         expectNoop(mock, true); // Need it because we reuse the connection.
   1092         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1093                 new String[] {
   1094                 "* 1 eXISTS",
   1095                 getNextTag(true) + " oK [rEAD-wRITE]"
   1096                 });
   1097 
   1098         folder.open(OpenMode.READ_WRITE, null);
   1099         assertTrue(folder.exists());
   1100         assertEquals(1, folder.getMessageCount());
   1101         assertEquals(OpenMode.READ_WRITE, folder.getMode());
   1102 
   1103         assertTrue(folder.isOpen());
   1104         folder.close(false);
   1105         assertFalse(folder.isOpen());
   1106 
   1107         // READ-ONLY
   1108         expectNoop(mock, true); // Need it because we reuse the connection.
   1109         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1110                 new String[] {
   1111                 "* 2 eXISTS",
   1112                 getNextTag(true) + " oK [rEAD-oNLY]"
   1113                 });
   1114 
   1115         folder.open(OpenMode.READ_WRITE, null);
   1116         assertTrue(folder.exists());
   1117         assertEquals(2, folder.getMessageCount());
   1118         assertEquals(OpenMode.READ_ONLY, folder.getMode());
   1119 
   1120         // Try to re-open as read-write.  Should send SELECT again.
   1121         expectNoop(mock, true); // Need it because we reuse the connection.
   1122         mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
   1123                 new String[] {
   1124                 "* 15 eXISTS",
   1125                 getNextTag(true) + " oK selected"
   1126                 });
   1127 
   1128         folder.open(OpenMode.READ_WRITE, null);
   1129         assertTrue(folder.exists());
   1130         assertEquals(15, folder.getMessageCount());
   1131         assertEquals(OpenMode.READ_WRITE, folder.getMode());
   1132     }
   1133 
   1134     public void testExists() throws Exception {
   1135         MockTransport mock = openAndInjectMockTransport();
   1136         expectLogin(mock);
   1137 
   1138         // Folder exists
   1139         Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
   1140         mock.expect(getNextTag(false) + " STATUS \\\"&ZeVnLIqe-\\\" \\(UIDVALIDITY\\)",
   1141                 new String[] {
   1142                 "* sTATUS \"&ZeVnLIqe-\" (mESSAGES 10)",
   1143                 getNextTag(true) + " oK SUCCESS"
   1144                 });
   1145 
   1146         assertTrue(folder.exists());
   1147 
   1148         // Connection verification
   1149         expectNoop(mock, true);
   1150 
   1151         // Doesn't exist
   1152         folder = mStore.getFolder("no such folder");
   1153         mock.expect(getNextTag(false) + " STATUS \\\"no such folder\\\" \\(UIDVALIDITY\\)",
   1154                 new String[] {
   1155                 getNextTag(true) + " NO No such folder!"
   1156                 });
   1157 
   1158         assertFalse(folder.exists());
   1159     }
   1160 
   1161     public void testCreate() throws Exception {
   1162         MockTransport mock = openAndInjectMockTransport();
   1163         expectLogin(mock);
   1164 
   1165         // Success
   1166         Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
   1167 
   1168         assertTrue(folder.canCreate(FolderType.HOLDS_MESSAGES));
   1169 
   1170         mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
   1171                 new String[] {
   1172                 getNextTag(true) + " oK Success"
   1173                 });
   1174 
   1175         assertTrue(folder.create(FolderType.HOLDS_MESSAGES));
   1176 
   1177         // Connection verification
   1178         expectNoop(mock, true);
   1179 
   1180         // Failure
   1181         mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
   1182                 new String[] {
   1183                 getNextTag(true) + " nO Can't create folder"
   1184                 });
   1185 
   1186         assertFalse(folder.create(FolderType.HOLDS_MESSAGES));
   1187     }
   1188 
   1189     public void testCopy() throws Exception {
   1190         MockTransport mock = openAndInjectMockTransport();
   1191         setupOpenFolder(mock);
   1192         mFolder.open(OpenMode.READ_WRITE, null);
   1193 
   1194         Folder folderTo = mStore.getFolder("\u65E5\u672C\u8A9E");
   1195         Message[] messages = new Message[] {
   1196                 mFolder.createMessage("11"),
   1197                 mFolder.createMessage("12"),
   1198                 };
   1199 
   1200         mock.expect(getNextTag(false) + " UID COPY 11\\,12 \\\"&ZeVnLIqe-\\\"",
   1201                 new String[] {
   1202                 getNextTag(true) + " oK copy completed"
   1203                 });
   1204 
   1205         mFolder.copyMessages(messages, folderTo, null);
   1206 
   1207         // TODO: Test NO response. (src message not found)
   1208     }
   1209 
   1210     public void testGetUnreadMessageCount() throws Exception {
   1211         MockTransport mock = openAndInjectMockTransport();
   1212         setupOpenFolder(mock);
   1213         mFolder.open(OpenMode.READ_WRITE, null);
   1214 
   1215         mock.expect(getNextTag(false) + " STATUS \\\"" + FOLDER_ENCODED + "\\\" \\(UNSEEN\\)",
   1216                 new String[] {
   1217                 "* sTATUS \"" + FOLDER_ENCODED + "\" (X 1 uNSEEN 123)",
   1218                 getNextTag(true) + " oK copy completed"
   1219                 });
   1220 
   1221         assertEquals(123, mFolder.getUnreadMessageCount());
   1222     }
   1223 
   1224     public void testExpunge() throws Exception {
   1225         MockTransport mock = openAndInjectMockTransport();
   1226         setupOpenFolder(mock);
   1227         mFolder.open(OpenMode.READ_WRITE, null);
   1228 
   1229         mock.expect(getNextTag(false) + " EXPUNGE",
   1230                 new String[] {
   1231                 getNextTag(true) + " oK success"
   1232                 });
   1233 
   1234         mFolder.expunge();
   1235 
   1236         // TODO: Test NO response. (permission denied)
   1237     }
   1238 
   1239     public void testSetFlags() throws Exception {
   1240         MockTransport mock = openAndInjectMockTransport();
   1241         setupOpenFolder(mock);
   1242         mFolder.open(OpenMode.READ_WRITE, null);
   1243 
   1244         Message[] messages = new Message[] {
   1245                 mFolder.createMessage("11"),
   1246                 mFolder.createMessage("12"),
   1247                 };
   1248 
   1249         // Set
   1250         mock.expect(
   1251                 getNextTag(false) + " UID STORE 11\\,12 \\+FLAGS.SILENT \\(\\\\FLAGGED \\\\SEEN\\)",
   1252                 new String[] {
   1253                 getNextTag(true) + " oK success"
   1254                 });
   1255         mFolder.setFlags(messages, new Flag[] {Flag.FLAGGED, Flag.SEEN}, true);
   1256 
   1257         // Clear
   1258         mock.expect(
   1259                 getNextTag(false) + " UID STORE 11\\,12 \\-FLAGS.SILENT \\(\\\\DELETED\\)",
   1260                 new String[] {
   1261                 getNextTag(true) + " oK success"
   1262                 });
   1263         mFolder.setFlags(messages, new Flag[] {Flag.DELETED}, false);
   1264 
   1265         // TODO: Test NO response. (src message not found)
   1266     }
   1267 
   1268     public void testSearchForUids() throws Exception {
   1269         MockTransport mock = openAndInjectMockTransport();
   1270         setupOpenFolder(mock);
   1271         mFolder.open(OpenMode.READ_WRITE, null);
   1272 
   1273         // Single results
   1274         mock.expect(
   1275                 getNextTag(false) + " UID SEARCH X",
   1276                 new String[] {
   1277                         "* sEARCH 1",
   1278                         getNextTag(true) + " oK success"
   1279                 });
   1280         MoreAsserts.assertEquals(new String[] {
   1281                 "1"
   1282                 }, mFolder.searchForUids("X"));
   1283 
   1284         // Multiple results, including SEARCH with no UIDs.
   1285         mock.expect(
   1286                 getNextTag(false) + " UID SEARCH UID 123",
   1287                 new String[] {
   1288                         "* sEARCH 123 4 567",
   1289                         "* search",
   1290                         "* sEARCH 0",
   1291                         "* SEARCH",
   1292                         "* sEARCH 100 200 300",
   1293                         getNextTag(true) + " oK success"
   1294                 });
   1295         MoreAsserts.assertEquals(new String[] {
   1296                 "123", "4", "567", "0", "100", "200", "300"
   1297                 }, mFolder.searchForUids("UID 123"));
   1298 
   1299         // NO result
   1300         mock.expect(
   1301                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1302                 new String[] {
   1303                         getNextTag(true) + " nO not found"
   1304                 });
   1305         MoreAsserts.assertEquals(new String[] {
   1306                 }, mFolder.searchForUids("SOME CRITERIA"));
   1307 
   1308         // OK result, but result is empty. (Probably against RFC)
   1309         mock.expect(
   1310                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1311                 new String[] {
   1312                         getNextTag(true) + " oK success"
   1313                 });
   1314         MoreAsserts.assertEquals(new String[] {
   1315                 }, mFolder.searchForUids("SOME CRITERIA"));
   1316 
   1317         // OK result with empty search response.
   1318         mock.expect(
   1319                 getNextTag(false) + " UID SEARCH SOME CRITERIA",
   1320                 new String[] {
   1321                         "* search",
   1322                         getNextTag(true) + " oK success"
   1323                 });
   1324         MoreAsserts.assertEquals(new String[] {
   1325                 }, mFolder.searchForUids("SOME CRITERIA"));
   1326     }
   1327 
   1328 
   1329     public void testGetMessage() throws Exception {
   1330         MockTransport mock = openAndInjectMockTransport();
   1331         setupOpenFolder(mock);
   1332         mFolder.open(OpenMode.READ_WRITE, null);
   1333 
   1334         // Found
   1335         mock.expect(
   1336                 getNextTag(false) + " UID SEARCH UID 123",
   1337                 new String[] {
   1338                     "* sEARCH 123",
   1339                 getNextTag(true) + " oK success"
   1340                 });
   1341         assertEquals("123", mFolder.getMessage("123").getUid());
   1342 
   1343         // Not found
   1344         mock.expect(
   1345                 getNextTag(false) + " UID SEARCH UID 123",
   1346                 new String[] {
   1347                 getNextTag(true) + " nO not found"
   1348                 });
   1349         assertNull(mFolder.getMessage("123"));
   1350     }
   1351 
   1352     /** Test for getMessages(int, int, MessageRetrievalListener) */
   1353     public void testGetMessages1() throws Exception {
   1354         MockTransport mock = openAndInjectMockTransport();
   1355         setupOpenFolder(mock);
   1356         mFolder.open(OpenMode.READ_WRITE, null);
   1357 
   1358         // Found
   1359         mock.expect(
   1360                 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
   1361                 new String[] {
   1362                     "* sEARCH 3 4",
   1363                 getNextTag(true) + " oK success"
   1364                 });
   1365 
   1366         checkMessageUids(new String[] {"3", "4"}, mFolder.getMessages(3, 5, null));
   1367 
   1368         // Not found
   1369         mock.expect(
   1370                 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
   1371                 new String[] {
   1372                 getNextTag(true) + " nO not found"
   1373                 });
   1374 
   1375         checkMessageUids(new String[] {}, mFolder.getMessages(3, 5, null));
   1376     }
   1377 
   1378     /**
   1379      * Test for getMessages(String[] uids, MessageRetrievalListener) where uids != null.
   1380      * (testGetMessages3() covers the case where uids == null.)
   1381      */
   1382     public void testGetMessages2() throws Exception {
   1383         MockTransport mock = openAndInjectMockTransport();
   1384         setupOpenFolder(mock);
   1385         mFolder.open(OpenMode.READ_WRITE, null);
   1386 
   1387         // No command will be sent
   1388         checkMessageUids(new String[] {"3", "4", "5"},
   1389                 mFolder.getMessages(new String[] {"3", "4", "5"}, null));
   1390 
   1391         checkMessageUids(new String[] {},
   1392                 mFolder.getMessages(new String[] {}, null));
   1393     }
   1394 
   1395     /**
   1396      * Test for getMessages(MessageRetrievalListener), which is the same as
   1397      * getMessages(String[] uids, MessageRetrievalListener) where uids == null.
   1398      */
   1399     public void testGetMessages3() throws Exception {
   1400         MockTransport mock = openAndInjectMockTransport();
   1401         setupOpenFolder(mock);
   1402         mFolder.open(OpenMode.READ_WRITE, null);
   1403 
   1404         mock.expect(
   1405                 getNextTag(false) + " UID SEARCH 1:\\* NOT DELETED",
   1406                 new String[] {
   1407                     "* sEARCH 3 4 5",
   1408                 getNextTag(true) + " OK success"
   1409                 });
   1410         checkMessageUids(new String[] {"3", "4", "5"},
   1411                 mFolder.getMessages(null));
   1412     }
   1413 
   1414     private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) {
   1415         ArrayList<String> list = new ArrayList<String>();
   1416         for (Message m : actualMessages) {
   1417             list.add(m.getUid());
   1418         }
   1419         MoreAsserts.assertEquals(expectedUids, list.toArray(new String[0]) );
   1420     }
   1421 
   1422     /**
   1423      * Test for {@link ImapStore#getConnection} and {@link ImapStore#keepConnectionForReuse}
   1424      */
   1425     public void testGetConnection() throws Exception {
   1426         MockTransport mock = openAndInjectMockTransport();
   1427 
   1428         // Start: No pooled connections.
   1429         assertEquals(0, mStore.getConnectionPoolForTest().size());
   1430 
   1431         // Get 1st connection.
   1432         final ImapConnection con1 = mStore.getConnection();
   1433         assertNotNull(con1);
   1434         assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
   1435         assertFalse(con1.isTransportOpenForTest()); // Transport not open yet.
   1436 
   1437         // Open con1
   1438         expectLogin(mock);
   1439         con1.open();
   1440         assertTrue(con1.isTransportOpenForTest());
   1441 
   1442         // Get 2nd connection.
   1443         final ImapConnection con2 = mStore.getConnection();
   1444         assertNotNull(con2);
   1445         assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
   1446         assertFalse(con2.isTransportOpenForTest()); // Transport not open yet.
   1447 
   1448         // con1 != con2
   1449         assertNotSame(con1, con2);
   1450 
   1451         // Open con2
   1452         expectLogin(mock);
   1453         con2.open();
   1454         assertTrue(con1.isTransportOpenForTest());
   1455 
   1456         // Now we have two open connections: con1 and con2
   1457 
   1458         // Save con1 in the pool.
   1459         mStore.poolConnection(con1);
   1460         assertEquals(1, mStore.getConnectionPoolForTest().size());
   1461 
   1462         // Get another connection.  Should get con1, after verifying the connection.
   1463         mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + " oK success"});
   1464 
   1465         final ImapConnection con1b = mStore.getConnection();
   1466         assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
   1467         assertSame(con1, con1b);
   1468         assertTrue(con1.isTransportOpenForTest()); // We opened it.
   1469 
   1470         // Save con2.
   1471         mStore.poolConnection(con2);
   1472         assertEquals(1, mStore.getConnectionPoolForTest().size());
   1473 
   1474         // Try to get connection, but this time, connection gets closed.
   1475         mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + "* bYE bye"});
   1476         final ImapConnection con3 = mStore.getConnection();
   1477         assertNotNull(con3);
   1478         assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
   1479 
   1480         // It should be a new connection.
   1481         assertNotSame(con1, con3);
   1482         assertNotSame(con2, con3);
   1483     }
   1484 
   1485     public void testCheckSettings() throws Exception {
   1486         MockTransport mock = openAndInjectMockTransport();
   1487 
   1488         expectLogin(mock);
   1489         mStore.checkSettings();
   1490 
   1491         expectLogin(mock, new String[] {"* iD nIL", "oK"}, "nO authentication failed");
   1492         try {
   1493             mStore.checkSettings();
   1494             fail();
   1495         } catch (MessagingException expected) {
   1496         }
   1497     }
   1498 
   1499     // Compatibility tests...
   1500 
   1501     /**
   1502      * Getting an ALERT with a % mark in the message, which crashed the old parser.
   1503      */
   1504     public void testQuotaAlert() throws Exception {
   1505         MockTransport mock = openAndInjectMockTransport();
   1506         expectLogin(mock);
   1507 
   1508         // Success
   1509         Folder folder = mStore.getFolder("INBOX");
   1510 
   1511         // The following response was copied from an actual bug...
   1512         mock.expect(getNextTag(false) + " SELECT \"INBOX\"", new String[] {
   1513             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk $Forwarded Junk" +
   1514                     " $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old)",
   1515             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk" +
   1516                     " $Forwarded Junk $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old \\*)]",
   1517             "* 6406 EXISTS",
   1518             "* 0 RECENT",
   1519             "* OK [UNSEEN 5338]",
   1520             "* OK [UIDVALIDITY 1055957975]",
   1521             "* OK [UIDNEXT 449625]",
   1522             "* NO [ALERT] Mailbox is at 98% of quota",
   1523             getNextTag(true) + " OK [READ-WRITE] Completed"});
   1524         folder.open(OpenMode.READ_WRITE, null); // shouldn't crash.
   1525         assertEquals(6406, folder.getMessageCount());
   1526     }
   1527 
   1528     /**
   1529      * Apparently some servers send a size in the wrong format. e.g. 123E
   1530      */
   1531     public void testFetchBodyStructureMalformed() throws Exception {
   1532         final MockTransport mock = openAndInjectMockTransport();
   1533         setupOpenFolder(mock);
   1534         mFolder.open(OpenMode.READ_WRITE, null);
   1535         final Message message = mFolder.createMessage("1");
   1536 
   1537         final FetchProfile fp = new FetchProfile();
   1538         fp.add(FetchProfile.Item.STRUCTURE);
   1539         mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
   1540                 new String[] {
   1541                 "* 9 FETCH (UID 1 BODYSTRUCTURE (\"TEXT\" \"PLAIN\" ()" +
   1542                         " NIL NIL NIL 123E 3))", // 123E isn't a number!
   1543                 getNextTag(true) + " OK SUCCESS"
   1544         });
   1545         mFolder.fetch(new Message[] { message }, fp, null);
   1546 
   1547         // Check mime structure...
   1548         MoreAsserts.assertEquals(
   1549                 new String[] {"text/plain"},
   1550                 message.getHeader("Content-Type")
   1551                 );
   1552         assertNull(message.getHeader("Content-Transfer-Encoding"));
   1553         assertNull(message.getHeader("Content-ID"));
   1554 
   1555         // Doesn't have size=xxx
   1556         assertNull(message.getHeader("Content-Disposition"));
   1557     }
   1558 
   1559     /**
   1560      * Folder name with special chars in it.
   1561      *
   1562      * Gmail puts the folder name in the OK response, which crashed the old parser if there's a
   1563      * special char in the folder name.
   1564      */
   1565     public void testFolderNameWithSpecialChars() throws Exception {
   1566         final String FOLDER_1 = "@u88**%_St";
   1567         final String FOLDER_1_QUOTED = Pattern.quote(FOLDER_1);
   1568         final String FOLDER_2 = "folder test_06";
   1569 
   1570         MockTransport mock = openAndInjectMockTransport();
   1571         expectLogin(mock);
   1572 
   1573         // List folders.
   1574         mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
   1575                 new String[] {
   1576                 "* LIST () \"/\" \"" + FOLDER_1 + "\"",
   1577                 "* LIST () \"/\" \"" + FOLDER_2 + "\"",
   1578                 getNextTag(true) + " OK SUCCESS"
   1579                 });
   1580         final Folder[] folders = mStore.getPersonalNamespaces();
   1581 
   1582         ArrayList<String> list = new ArrayList<String>();
   1583         for (Folder f : folders) {
   1584             list.add(f.getName());
   1585         }
   1586         MoreAsserts.assertEquals(
   1587                 new String[] {FOLDER_1, FOLDER_2, "INBOX"},
   1588                 list.toArray(new String[0])
   1589                 );
   1590 
   1591         // Try to open the folders.
   1592         expectNoop(mock, true);
   1593         mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_1_QUOTED + "\"", new String[] {
   1594             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
   1595             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
   1596             "* 0 EXISTS",
   1597             "* 0 RECENT",
   1598             "* OK [UNSEEN 0]",
   1599             "* OK [UIDNEXT 1]",
   1600             getNextTag(true) + " OK [READ-WRITE] " + FOLDER_1});
   1601         folders[0].open(OpenMode.READ_WRITE, null);
   1602         folders[0].close(false);
   1603 
   1604         expectNoop(mock, true);
   1605         mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_2 + "\"", new String[] {
   1606             "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
   1607             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
   1608             "* 0 EXISTS",
   1609             "* 0 RECENT",
   1610             "* OK [UNSEEN 0]",
   1611             "* OK [UIDNEXT 1]",
   1612             getNextTag(true) + " OK [READ-WRITE] " + FOLDER_2});
   1613         folders[1].open(OpenMode.READ_WRITE, null);
   1614         folders[1].close(false);
   1615     }
   1616 
   1617     /**
   1618      * Callback for {@link #runAndExpectMessagingException}.
   1619      */
   1620     private interface RunAndExpectMessagingExceptionTarget {
   1621         public void run(MockTransport mockTransport) throws Exception;
   1622     }
   1623 
   1624     /**
   1625      * Set up the usual mock transport, open the folder,
   1626      * run {@link RunAndExpectMessagingExceptionTarget} and make sure a {@link MessagingException}
   1627      * is thrown.
   1628      */
   1629     private void runAndExpectMessagingException(RunAndExpectMessagingExceptionTarget target)
   1630             throws Exception {
   1631         try {
   1632             final MockTransport mockTransport = openAndInjectMockTransport();
   1633             setupOpenFolder(mockTransport);
   1634             mFolder.open(OpenMode.READ_WRITE, null);
   1635 
   1636             target.run(mockTransport);
   1637 
   1638             fail("MessagingException expected.");
   1639         } catch (MessagingException expected) {
   1640         }
   1641     }
   1642 
   1643     /**
   1644      * Make sure that IOExceptions are always converted to MessagingException.
   1645      */
   1646     public void testFetchIOException() throws Exception {
   1647         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1648             public void run(MockTransport mockTransport) throws Exception {
   1649                 mockTransport.expectIOException();
   1650 
   1651                 final Message message = mFolder.createMessage("1");
   1652                 final FetchProfile fp = new FetchProfile();
   1653                 fp.add(FetchProfile.Item.STRUCTURE);
   1654 
   1655                 mFolder.fetch(new Message[] { message }, fp, null);
   1656             }
   1657         });
   1658     }
   1659 
   1660     /**
   1661      * Make sure that IOExceptions are always converted to MessagingException.
   1662      */
   1663     public void testUnreadMessageCountIOException() throws Exception {
   1664         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1665             public void run(MockTransport mockTransport) throws Exception {
   1666                 mockTransport.expectIOException();
   1667 
   1668                 mFolder.getUnreadMessageCount();
   1669             }
   1670         });
   1671     }
   1672 
   1673     /**
   1674      * Make sure that IOExceptions are always converted to MessagingException.
   1675      */
   1676     public void testCopyMessagesIOException() throws Exception {
   1677         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1678             public void run(MockTransport mockTransport) throws Exception {
   1679                 mockTransport.expectIOException();
   1680 
   1681                 final Message message = mFolder.createMessage("1");
   1682                 final Folder folder = mStore.getFolder("test");
   1683 
   1684                 mFolder.copyMessages(new Message[] { message }, folder, null);
   1685             }
   1686         });
   1687     }
   1688 
   1689     /**
   1690      * Make sure that IOExceptions are always converted to MessagingException.
   1691      */
   1692     public void testSearchForUidsIOException() throws Exception {
   1693         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1694             public void run(MockTransport mockTransport) throws Exception {
   1695                 mockTransport.expectIOException();
   1696 
   1697                 mFolder.getMessage("uid");
   1698             }
   1699         });
   1700     }
   1701 
   1702     /**
   1703      * Make sure that IOExceptions are always converted to MessagingException.
   1704      */
   1705     public void testExpungeIOException() throws Exception {
   1706         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1707             public void run(MockTransport mockTransport) throws Exception {
   1708                 mockTransport.expectIOException();
   1709 
   1710                 mFolder.expunge();
   1711             }
   1712         });
   1713     }
   1714 
   1715     /**
   1716      * Make sure that IOExceptions are always converted to MessagingException.
   1717      */
   1718     public void testOpenIOException() throws Exception {
   1719         runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
   1720             public void run(MockTransport mockTransport) throws Exception {
   1721                 mockTransport.expectIOException();
   1722                 final Folder folder = mStore.getFolder("test");
   1723                 folder.open(OpenMode.READ_WRITE, null);
   1724             }
   1725         });
   1726     }
   1727 }
   1728