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