Home | History | Annotate | Download | only in store
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.mail.store;
     18 
     19 import android.content.Context;
     20 import android.test.AndroidTestCase;
     21 import android.test.suitebuilder.annotation.SmallTest;
     22 
     23 import com.android.email.Controller;
     24 import com.android.email.DBTestHelper;
     25 import com.android.email.mail.Transport;
     26 import com.android.email.mail.transport.MockTransport;
     27 import com.android.email.provider.ProviderTestUtils;
     28 import com.android.emailcommon.TempDirectory;
     29 import com.android.emailcommon.internet.MimeMessage;
     30 import com.android.emailcommon.mail.Address;
     31 import com.android.emailcommon.mail.FetchProfile;
     32 import com.android.emailcommon.mail.Flag;
     33 import com.android.emailcommon.mail.Folder;
     34 import com.android.emailcommon.mail.Folder.FolderType;
     35 import com.android.emailcommon.mail.Folder.OpenMode;
     36 import com.android.emailcommon.mail.Message;
     37 import com.android.emailcommon.mail.Message.RecipientType;
     38 import com.android.emailcommon.mail.MessagingException;
     39 import com.android.emailcommon.provider.Account;
     40 import com.android.emailcommon.provider.HostAuth;
     41 
     42 /**
     43  * This is a series of unit tests for the POP3 Store class.  These tests must be locally
     44  * complete - no server(s) required.
     45  */
     46 @SmallTest
     47 public class Pop3StoreUnitTests extends AndroidTestCase {
     48     final String UNIQUE_ID_1 = "20080909002219r1800rrjo9e00";
     49 
     50     final static int PER_MESSAGE_SIZE = 100;
     51 
     52     /* These values are provided by setUp() */
     53     private Pop3Store mStore = null;
     54     private Pop3Store.Pop3Folder mFolder = null;
     55 
     56     private Context mMockContext;
     57 
     58     /**
     59      * Setup code.  We generate a lightweight Pop3Store and Pop3Store.Pop3Folder.
     60      */
     61     @Override
     62     protected void setUp() throws Exception {
     63         super.setUp();
     64         mMockContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
     65                 getContext());
     66         Controller.getInstance(mMockContext).setProviderContext(mMockContext);
     67         Controller.getInstance(mMockContext).markForTest(true);
     68 
     69         // Use the target's (i.e. the Email application) context
     70         TempDirectory.setTempDirectory(mMockContext);
     71 
     72         // These are needed so we can get at the inner classes
     73         HostAuth testAuth = new HostAuth();
     74         Account testAccount = ProviderTestUtils.setupAccount("acct1", false, mMockContext);
     75 
     76         testAuth.setLogin("user", "password");
     77         testAuth.setConnection("pop3", "server", 999);
     78         testAccount.mHostAuthRecv = testAuth;
     79         testAccount.save(mMockContext);
     80         mStore = (Pop3Store) Pop3Store.newInstance(testAccount, mMockContext);
     81         mFolder = (Pop3Store.Pop3Folder) mStore.getFolder("INBOX");
     82     }
     83 
     84     /**
     85      * Test various sunny-day operations of UIDL parser for multi-line responses
     86      */
     87     public void testUIDLParserMulti() {
     88 
     89         // multi-line mode
     90         Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser();
     91 
     92         // Test basic in-list UIDL
     93         parser.parseMultiLine("101 " + UNIQUE_ID_1);
     94         assertEquals(101, parser.mMessageNumber);
     95         assertEquals(UNIQUE_ID_1, parser.mUniqueId);
     96         assertFalse(parser.mEndOfMessage);
     97         assertFalse(parser.mErr);
     98 
     99         //  Test end-of-list
    100         parser.parseMultiLine(".");
    101         assertTrue(parser.mEndOfMessage);
    102         assertFalse(parser.mErr);
    103     }
    104 
    105     /**
    106      * Test various sunny-day operations of UIDL parser for single-line responses
    107      */
    108     public void testUIDLParserSingle() {
    109 
    110         // single-line mode
    111         Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser();
    112 
    113         // Test single-message OK response
    114         parser.parseSingleLine("+OK 101 " + UNIQUE_ID_1);
    115         assertEquals(101, parser.mMessageNumber);
    116         assertEquals(UNIQUE_ID_1, parser.mUniqueId);
    117         assertTrue(parser.mEndOfMessage);
    118 
    119         // Test single-message ERR response
    120         parser.parseSingleLine("-ERR what???");
    121         assertTrue(parser.mErr);
    122     }
    123 
    124     /**
    125      * Test various rainy-day operations of the UIDL parser for multi-line responses
    126      * TODO other malformed responses
    127      */
    128     public void testUIDLParserMultiFail() {
    129         // multi-line mode
    130         Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser();
    131 
    132         // Test with null input
    133         boolean result;
    134         result = parser.parseMultiLine(null);
    135         assertFalse(result);
    136 
    137         // Test with empty input
    138         result = parser.parseMultiLine("");
    139         assertFalse(result);
    140     }
    141 
    142     /**
    143      * Test various rainy-day operations of the UIDL parser for single-line responses
    144      * TODO other malformed responses
    145      */
    146     public void testUIDLParserSingleFail() {
    147         // single-line mode
    148         Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser();
    149 
    150         // Test with null input
    151         boolean result;
    152         result = parser.parseSingleLine(null);
    153         assertFalse(result);
    154 
    155         // Test with empty input
    156         result = parser.parseSingleLine("");
    157         assertFalse(result);
    158     }
    159 
    160     /**
    161      * Tests that variants on the RFC-specified formatting of UIDL work properly.
    162      */
    163     public void testUIDLComcastVariant() {
    164 
    165         // multi-line mode
    166         Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser();
    167 
    168         // Comcast servers send multiple spaces in their darn UIDL strings.
    169         parser.parseMultiLine("101   " + UNIQUE_ID_1);
    170         assertEquals(101, parser.mMessageNumber);
    171         assertEquals(UNIQUE_ID_1, parser.mUniqueId);
    172         assertFalse(parser.mEndOfMessage);
    173         assertFalse(parser.mErr);
    174     }
    175 
    176     /**
    177      * Confirms simple non-SSL non-TLS login
    178      */
    179     public void testSimpleLogin() throws MessagingException {
    180 
    181         MockTransport mockTransport = openAndInjectMockTransport();
    182 
    183         // try to open it
    184         setupOpenFolder(mockTransport, 0, null);
    185         mFolder.open(OpenMode.READ_ONLY);
    186     }
    187 
    188     /**
    189      * TODO: Test with SSL negotiation (faked)
    190      * TODO: Test with SSL required but not supported
    191      * TODO: Test with TLS negotiation (faked)
    192      * TODO: Test with TLS required but not supported
    193      * TODO: Test calling getMessageCount(), getMessages(), etc.
    194      */
    195 
    196     /**
    197      * Test the operation of checkSettings(), which requires (a) a good open and (b) UIDL support.
    198      */
    199     public void testCheckSettings() throws MessagingException {
    200 
    201         MockTransport mockTransport = openAndInjectMockTransport();
    202 
    203         // scenario 1:  CAPA returns -ERR, so we try UIDL explicitly
    204         setupOpenFolder(mockTransport, 0, null);
    205         setupUidlSequence(mockTransport, 1);
    206         mockTransport.expect("QUIT", "");
    207         mStore.checkSettings();
    208 
    209         // scenario 2:  CAPA indicates UIDL, so we don't try UIDL
    210         setupOpenFolder(mockTransport, 0, "UIDL");
    211         mockTransport.expect("QUIT", "");
    212         mStore.checkSettings();
    213 
    214         // scenario 3:  CAPA returns -ERR, and UIDL fails
    215         try {
    216             setupOpenFolder(mockTransport, 0, null);
    217             mockTransport.expect("UIDL", "-ERR unsupported");
    218             mockTransport.expect("QUIT", "");
    219             mStore.checkSettings();
    220             fail("MessagingException was expected due to UIDL unsupported.");
    221         } catch (MessagingException me) {
    222             // this is expected, so eat it
    223         }
    224     }
    225 
    226     /**
    227      * Test a strange case that causes open to proceed without mCapabilities
    228      *  open - fail with "-" error code
    229      *  then check capabilities
    230      */
    231     public void testCheckSettingsCapabilities() throws MessagingException {
    232 
    233         MockTransport mockTransport = openAndInjectMockTransport();
    234 
    235         // First, preload an open that fails for some reason
    236         mockTransport.expect(null, "-ERR from the Mock Transport.");
    237 
    238         // And watch it fail
    239         try {
    240             Pop3Store.Pop3Folder folder = mStore.new Pop3Folder("INBOX");
    241             folder.open(OpenMode.READ_WRITE);
    242             fail("Should have thrown exception");
    243         } catch (MessagingException me) {
    244             // Expected - continue.
    245         }
    246 
    247         // Now try again (assuming a slightly different connection setup - successful)
    248         // Note, checkSettings is going to try to close the connection again, so we expect
    249         // one extra QUIT before we spin it up again
    250         mockTransport.expect("QUIT", "");
    251         mockTransport.expectClose();
    252         setupOpenFolder(mockTransport, 0, "UIDL");
    253         mockTransport.expect("QUIT", "");
    254         mStore.checkSettings();
    255     }
    256 
    257     /**
    258      * Test small Store & Folder functions that manage folders & namespace
    259      */
    260     public void testStoreFoldersFunctions() {
    261 
    262         // getPersonalNamespaces() always returns INBOX folder
    263         Folder[] folders = mStore.updateFolders();
    264         assertEquals(1, folders.length);
    265         assertSame(mFolder, folders[0]);
    266 
    267         // getName() returns the name we were created with.  If "inbox", converts to INBOX
    268         assertEquals("INBOX", mFolder.getName());
    269         Pop3Store.Pop3Folder folderMixedCaseInbox = mStore.new Pop3Folder("iNbOx");
    270         assertEquals("INBOX", folderMixedCaseInbox.getName());
    271         Pop3Store.Pop3Folder folderNotInbox = mStore.new Pop3Folder("NOT-INBOX");
    272         assertEquals("NOT-INBOX", folderNotInbox.getName());
    273 
    274         // exists() true if name is INBOX
    275         assertTrue(mFolder.exists());
    276         assertTrue(folderMixedCaseInbox.exists());
    277         assertFalse(folderNotInbox.exists());
    278     }
    279 
    280     /**
    281      * Test small Folder functions that don't really do anything in Pop3
    282      */
    283     public void testSmallFolderFunctions() {
    284 
    285         // getMode() returns OpenMode.READ_WRITE
    286         assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
    287 
    288         // canCreate() && create() return false
    289         assertFalse(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
    290         assertFalse(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
    291         assertFalse(mFolder.create(FolderType.HOLDS_FOLDERS));
    292         assertFalse(mFolder.create(FolderType.HOLDS_MESSAGES));
    293 
    294         // getUnreadMessageCount() always returns -1
    295         assertEquals(-1, mFolder.getUnreadMessageCount());
    296 
    297         // getPermanentFlags() returns { Flag.DELETED }
    298         Flag[] flags = mFolder.getPermanentFlags();
    299         assertEquals(1, flags.length);
    300         assertEquals(Flag.DELETED, flags[0]);
    301 
    302         // appendMessages(Message[] messages) does nothing
    303         mFolder.appendMessages(null);
    304 
    305         // delete(boolean recurse) does nothing
    306         // TODO - it should!
    307         mFolder.delete(false);
    308 
    309         // expunge() returns null
    310         assertNull(mFolder.expunge());
    311 
    312         // copyMessages() is unsupported
    313         try {
    314             mFolder.copyMessages(null, null, null);
    315             fail("Exception not thrown by copyMessages()");
    316         } catch (UnsupportedOperationException e) {
    317             // expected - succeed
    318         }
    319     }
    320 
    321     /**
    322      * Lightweight test to confirm that POP3 hasn't implemented any folder roles yet.
    323      */
    324     public void testNoFolderRolesYet() {
    325         Folder[] remoteFolders = mStore.updateFolders();
    326         for (Folder folder : remoteFolders) {
    327             assertEquals(Folder.FolderRole.UNKNOWN, folder.getRole());
    328         }
    329     }
    330 
    331     /**
    332      * Lightweight test to confirm that POP3 is requesting sent-message-upload.
    333      */
    334     public void testSentUploadRequested() {
    335         assertTrue(mStore.requireCopyMessageToSentFolder());
    336     }
    337 
    338     /**
    339      * Test the process of opening and indexing a mailbox with one unread message in it.
    340      *
    341      * TODO should create an instrumented listener to confirm all expected callbacks.  Then use
    342      * it everywhere we could have passed a message listener.
    343      */
    344     public void testOneUnread() throws MessagingException {
    345 
    346         MockTransport mockTransport = openAndInjectMockTransport();
    347 
    348         checkOneUnread(mockTransport);
    349     }
    350 
    351     /**
    352      * Test the process of opening and getting message by uid.
    353      */
    354     public void testGetMessageByUid() throws MessagingException {
    355 
    356         MockTransport mockTransport = openAndInjectMockTransport();
    357 
    358         setupOpenFolder(mockTransport, 2, null);
    359         mFolder.open(OpenMode.READ_WRITE);
    360         // check message count
    361         assertEquals(2, mFolder.getMessageCount());
    362 
    363         // setup 2 messages
    364         setupUidlSequence(mockTransport, 2);
    365         String uid1 = getSingleMessageUID(1);
    366         String uid2 = getSingleMessageUID(2);
    367         String uid3 = getSingleMessageUID(3);
    368 
    369         Message msg1 = mFolder.getMessage(uid1);
    370         assertTrue("message with uid1", msg1 != null);
    371 
    372         // uid3 does not exist
    373         Message msg3 = mFolder.getMessage(uid3);
    374         assertTrue("message with uid3", msg3 == null);
    375 
    376         Message msg2 = mFolder.getMessage(uid2);
    377         assertTrue("message with uid2", msg2 != null);
    378     }
    379 
    380     /**
    381      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    382      * things should happen:  We should see an intermediate failure that makes sense, and the next
    383      * operation should reopen properly.
    384      *
    385      * There are multiple versions of this test because we are simulating the steps of
    386      * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit
    387      * further along in each case, to test various recovery points.
    388      *
    389      * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in
    390      * Pop3Folder.getMessages(), due to a closure before the UIDL command completes.
    391      */
    392     public void testCatchClosed1a() throws MessagingException {
    393 
    394         MockTransport mockTransport = openAndInjectMockTransport();
    395 
    396         openFolderWithMessage(mockTransport);
    397 
    398         // cause the next sequence to fail on the readLine() calls
    399         mockTransport.closeInputStream();
    400 
    401         // index the message(s) - it should fail, because our stream is broken
    402         try {
    403             setupUidlSequence(mockTransport, 1);
    404             Message[] messages = mFolder.getMessages(1, 1, null);
    405             assertEquals(1, messages.length);
    406             assertEquals(getSingleMessageUID(1), messages[0].getUid());
    407             fail("Broken stream should cause getMessages() to throw.");
    408         } catch(MessagingException me) {
    409             // success
    410         }
    411 
    412         // At this point the UI would display connection error, which is fine.  Now, the real
    413         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    414         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    415 
    416         // confirm that we're closed at this point
    417         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    418 
    419         // and confirm that the next connection will be OK
    420         checkOneUnread(mockTransport);
    421     }
    422 
    423     /**
    424      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    425      * things should happen:  We should see an intermediate failure that makes sense, and the next
    426      * operation should reopen properly.
    427      *
    428      * There are multiple versions of this test because we are simulating the steps of
    429      * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit
    430      * further along in each case, to test various recovery points.
    431      *
    432      * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in
    433      * Pop3Folder.getMessages(), due to non-numeric data in a multi-line UIDL.
    434      */
    435     public void testCatchClosed1b() throws MessagingException {
    436 
    437         MockTransport mockTransport = openAndInjectMockTransport();
    438 
    439         openFolderWithMessage(mockTransport);
    440 
    441         // index the message(s) - it should fail, because our stream is broken
    442         try {
    443             // setupUidlSequence(mockTransport, 1);
    444             mockTransport.expect("UIDL", "+OK sending UIDL list");
    445             mockTransport.expect(null, "bad-data" + " " + "THE-UIDL");
    446             mockTransport.expect(null, ".");
    447 
    448             Message[] messages = mFolder.getMessages(1, 1, null);
    449             fail("Bad UIDL should cause getMessages() to throw.");
    450         } catch(MessagingException me) {
    451             // success
    452         }
    453 
    454         // At this point the UI would display connection error, which is fine.  Now, the real
    455         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    456         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    457 
    458         // confirm that we're closed at this point
    459         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    460 
    461         // and confirm that the next connection will be OK
    462         checkOneUnread(mockTransport);
    463     }
    464 
    465     /**
    466      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    467      * things should happen:  We should see an intermediate failure that makes sense, and the next
    468      * operation should reopen properly.
    469      *
    470      * There are multiple versions of this test because we are simulating the steps of
    471      * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit
    472      * further along in each case, to test various recovery points.
    473      *
    474      * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in
    475      * Pop3Folder.getMessages(), due to non-numeric data in a single-line UIDL.
    476      */
    477     public void testCatchClosed1c() throws MessagingException {
    478 
    479         MockTransport mockTransport = openAndInjectMockTransport();
    480 
    481         // openFolderWithMessage(mockTransport);
    482         setupOpenFolder(mockTransport, 6000, null);
    483         mFolder.open(OpenMode.READ_ONLY);
    484         assertEquals(6000, mFolder.getMessageCount());
    485 
    486         // index the message(s) - it should fail, because our stream is broken
    487         try {
    488             // setupUidlSequence(mockTransport, 1);
    489             mockTransport.expect("UIDL 1", "+OK " + "bad-data" + " " + "THE-UIDL");
    490 
    491             Message[] messages = mFolder.getMessages(1, 1, null);
    492             fail("Bad UIDL should cause getMessages() to throw.");
    493         } catch(MessagingException me) {
    494             // success
    495         }
    496 
    497         // At this point the UI would display connection error, which is fine.  Now, the real
    498         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    499         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    500 
    501         // confirm that we're closed at this point
    502         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    503 
    504         // and confirm that the next connection will be OK
    505         checkOneUnread(mockTransport);
    506     }
    507 
    508     /**
    509      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    510      * things should happen:  We should see an intermediate failure that makes sense, and the next
    511      * operation should reopen properly.
    512      *
    513      * There are multiple versions of this test because we are simulating the steps of
    514      * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit
    515      * further along in each case, to test various recovery points.
    516      *
    517      * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in
    518      * Pop3Folder.fetch(), for a failure in the call to indexUids().
    519      */
    520     public void testCatchClosed2() throws MessagingException {
    521 
    522         MockTransport mockTransport = openAndInjectMockTransport();
    523 
    524         openFolderWithMessage(mockTransport);
    525 
    526         // index the message(s)
    527         setupUidlSequence(mockTransport, 1);
    528         Message[] messages = mFolder.getMessages(1, 1, null);
    529         assertEquals(1, messages.length);
    530         assertEquals(getSingleMessageUID(1), messages[0].getUid());
    531 
    532         // cause the next sequence to fail on the readLine() calls
    533         mockTransport.closeInputStream();
    534 
    535         try {
    536             // try the basic fetch of flags & envelope
    537             setupListSequence(mockTransport, 1);
    538             FetchProfile fp = new FetchProfile();
    539             fp.add(FetchProfile.Item.FLAGS);
    540             fp.add(FetchProfile.Item.ENVELOPE);
    541             mFolder.fetch(messages, fp, null);
    542             assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
    543             fail("Broken stream should cause fetch() to throw.");
    544         }
    545         catch(MessagingException me) {
    546             // success
    547         }
    548 
    549         // At this point the UI would display connection error, which is fine.  Now, the real
    550         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    551         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    552 
    553         // confirm that we're closed at this point
    554         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    555 
    556         // and confirm that the next connection will be OK
    557         checkOneUnread(mockTransport);
    558     }
    559 
    560     /**
    561      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    562      * things should happen:  We should see an intermediate failure that makes sense, and the next
    563      * operation should reopen properly.
    564      *
    565      * There are multiple versions of this test because we have to check additional places where
    566      * Pop3Store and/or Pop3Folder should be dealing with IOErrors.
    567      *
    568      * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in
    569      * Pop3Folder.fetch(), for a failure in the call to fetchEnvelope().
    570      */
    571     public void testCatchClosed2a() throws MessagingException {
    572 
    573         MockTransport mockTransport = openAndInjectMockTransport();
    574 
    575         openFolderWithMessage(mockTransport);
    576 
    577         // index the message(s)
    578         setupUidlSequence(mockTransport, 1);
    579         Message[] messages = mFolder.getMessages(1, 1, null);
    580         assertEquals(1, messages.length);
    581         assertEquals(getSingleMessageUID(1), messages[0].getUid());
    582 
    583         // try the basic fetch of flags & envelope, but the LIST command fails
    584         setupBrokenListSequence(mockTransport, 1);
    585         try {
    586             FetchProfile fp = new FetchProfile();
    587             fp.add(FetchProfile.Item.FLAGS);
    588             fp.add(FetchProfile.Item.ENVELOPE);
    589             mFolder.fetch(messages, fp, null);
    590             assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
    591             fail("Broken stream should cause fetch() to throw.");
    592         } catch(MessagingException me) {
    593             // success
    594         }
    595 
    596         // At this point the UI would display connection error, which is fine.  Now, the real
    597         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    598         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    599 
    600         // confirm that we're closed at this point
    601         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    602 
    603         // and confirm that the next connection will be OK
    604         checkOneUnread(mockTransport);
    605     }
    606 
    607     /**
    608      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    609      * things should happen:  We should see an intermediate failure that makes sense, and the next
    610      * operation should reopen properly.
    611      *
    612      * There are multiple versions of this test because we are simulating the steps of
    613      * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit
    614      * further along in each case, to test various recovery points.
    615      *
    616      * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in
    617      * Pop3Folder.fetch().
    618      */
    619     public void testCatchClosed3() throws MessagingException {
    620 
    621         MockTransport mockTransport = openAndInjectMockTransport();
    622 
    623         openFolderWithMessage(mockTransport);
    624 
    625         // index the message(s)
    626         setupUidlSequence(mockTransport, 1);
    627         Message[] messages = mFolder.getMessages(1, 1, null);
    628         assertEquals(1, messages.length);
    629         assertEquals(getSingleMessageUID(1), messages[0].getUid());
    630 
    631         // try the basic fetch of flags & envelope
    632         setupListSequence(mockTransport, 1);
    633         FetchProfile fp = new FetchProfile();
    634         fp.add(FetchProfile.Item.FLAGS);
    635         fp.add(FetchProfile.Item.ENVELOPE);
    636         mFolder.fetch(messages, fp, null);
    637         assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
    638 
    639         // cause the next sequence to fail on the readLine() calls
    640         mockTransport.closeInputStream();
    641 
    642         try {
    643             // now try fetching the message
    644             setupSingleMessage(mockTransport, 1, false);
    645             fp = new FetchProfile();
    646             fp.add(FetchProfile.Item.BODY);
    647             mFolder.fetch(messages, fp, null);
    648             checkFetchedMessage(messages[0], 1, false);
    649             fail("Broken stream should cause fetch() to throw.");
    650         }
    651         catch(MessagingException me) {
    652             // success
    653         }
    654 
    655         // At this point the UI would display connection error, which is fine.  Now, the real
    656         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    657         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    658 
    659         // confirm that we're closed at this point
    660         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    661 
    662         // and confirm that the next connection will be OK
    663         checkOneUnread(mockTransport);
    664     }
    665 
    666     /**
    667      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    668      * things should happen:  We should see an intermediate failure that makes sense, and the next
    669      * operation should reopen properly.
    670      *
    671      * There are multiple versions of this test because we have to check additional places where
    672      * Pop3Store and/or Pop3Folder should be dealing with IOErrors.
    673      *
    674      * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in
    675      * Pop3Folder.setFlags().
    676      */
    677     public void testCatchClosed4() throws MessagingException {
    678 
    679         MockTransport mockTransport = openAndInjectMockTransport();
    680 
    681         openFolderWithMessage(mockTransport);
    682 
    683         // index the message(s)
    684         setupUidlSequence(mockTransport, 1);
    685         Message[] messages = mFolder.getMessages(1, 1, null);
    686         assertEquals(1, messages.length);
    687         assertEquals(getSingleMessageUID(1), messages[0].getUid());
    688 
    689         // cause the next sequence to fail on the readLine() calls
    690         mockTransport.closeInputStream();
    691 
    692         // delete 'em all - should fail because of broken stream
    693         try {
    694             mockTransport.expect("DELE 1", "+OK message deleted");
    695             mFolder.setFlags(messages, new Flag[] { Flag.DELETED }, true);
    696             fail("Broken stream should cause fetch() to throw.");
    697         }
    698         catch(MessagingException me) {
    699             // success
    700         }
    701 
    702         // At this point the UI would display connection error, which is fine.  Now, the real
    703         // test is, can we recover?  So I'll just repeat the above steps, without the failure.
    704         // NOTE: everything from here down is copied from testOneUnread() and should be consolidated
    705 
    706         // confirm that we're closed at this point
    707         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    708 
    709         // and confirm that the next connection will be OK
    710         checkOneUnread(mockTransport);
    711     }
    712 
    713     /**
    714      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    715      * things should happen:  We should see an intermediate failure that makes sense, and the next
    716      * operation should reopen properly.
    717      *
    718      * There are multiple versions of this test because we have to check additional places where
    719      * Pop3Store and/or Pop3Folder should be dealing with IOErrors.
    720      *
    721      * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in
    722      * Pop3Folder.open().
    723      */
    724     public void testCatchClosed5() {
    725         // TODO cannot write this test until we can inject stream closures mid-sequence
    726     }
    727 
    728     /**
    729      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    730      * things should happen:  We should see an intermediate failure that makes sense, and the next
    731      * operation should reopen properly.
    732      *
    733      * There are multiple versions of this test because we have to check additional places where
    734      * Pop3Store and/or Pop3Folder should be dealing with IOErrors.
    735      *
    736      * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in
    737      * Pop3Folder.open() (when it calls STAT and the response is empty of garbagey).
    738      */
    739     public void testCatchClosed6a() throws MessagingException {
    740 
    741         MockTransport mockTransport = openAndInjectMockTransport();
    742 
    743         // like openFolderWithMessage(mockTransport) but with a broken STAT report (empty response)
    744         setupOpenFolder(mockTransport, -1, null);
    745         try {
    746             mFolder.open(OpenMode.READ_ONLY);
    747             fail("Broken STAT should cause open() to throw.");
    748         } catch(MessagingException me) {
    749             // success
    750         }
    751 
    752         // At this point the UI would display connection error, which is fine.  Now, the real
    753         // test is, can we recover?  So I'll try a new connection, without the failure.
    754 
    755         // confirm that we're closed at this point
    756         assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen());
    757 
    758         // and confirm that the next connection will be OK
    759         checkOneUnread(mockTransport);
    760     }
    761 
    762     /**
    763      * Test the scenario where the transport is "open" but not really (e.g. server closed).  Two
    764      * things should happen:  We should see an intermediate failure that makes sense, and the next
    765      * operation should reopen properly.
    766      *
    767      * There are multiple versions of this test because we have to check additional places where
    768      * Pop3Store and/or Pop3Folder should be dealing with IOErrors.
    769      *
    770      * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in
    771      * Pop3Folder.open() (when it calls STAT, and there is no response at all).
    772      */
    773     public void testCatchClosed6b() {
    774         // TODO cannot write this test until we can inject stream closures mid-sequence
    775     }
    776 
    777     /**
    778      * Given an initialized mock transport, open it and attempt to "read" one unread message from
    779      * it.  This can be used as a basic test of functionality and it should be possible to call this
    780      * repeatedly (if you close the folder between calls).
    781      *
    782      * @param mockTransport the mock transport we're using
    783      */
    784     private void checkOneUnread(MockTransport mockTransport) throws MessagingException {
    785         openFolderWithMessage(mockTransport);
    786 
    787         // index the message(s)
    788         setupUidlSequence(mockTransport, 1);
    789         Message[] messages = mFolder.getMessages(1, 1, null);
    790         assertEquals(1, messages.length);
    791         assertEquals(getSingleMessageUID(1), messages[0].getUid());
    792 
    793         // try the basic fetch of flags & envelope
    794         setupListSequence(mockTransport, 1);
    795         FetchProfile fp = new FetchProfile();
    796         fp.add(FetchProfile.Item.FLAGS);
    797         fp.add(FetchProfile.Item.ENVELOPE);
    798         mFolder.fetch(messages, fp, null);
    799         assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
    800 
    801         // A side effect of how messages work is that if you get fields that are empty,
    802         // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom).  The
    803         // standard message parser needs to clear these before parsing.  Make sure that this
    804         // is happening.  (This doesn't affect IMAP, which reads the headers directly via
    805         // IMAP evelopes.)
    806         MimeMessage message = (MimeMessage) messages[0];
    807         message.getRecipients(RecipientType.TO);
    808         message.getRecipients(RecipientType.CC);
    809         message.getRecipients(RecipientType.BCC);
    810 
    811         // now try fetching the message
    812         setupSingleMessage(mockTransport, 1, false);
    813         fp = new FetchProfile();
    814         fp.add(FetchProfile.Item.BODY);
    815         mFolder.fetch(messages, fp, null);
    816         checkFetchedMessage(messages[0], 1, false);
    817     }
    818 
    819     /**
    820      * A group of tests to confirm that we're properly juggling the RETR and TOP commands.
    821      * Some servers (hello, live.com) support TOP but don't support CAPA.  So we ignore CAPA
    822      * and just try TOP.
    823      */
    824     public void testRetrVariants() throws MessagingException {
    825         MockTransport mockTransport = openAndInjectMockTransport();
    826         openFolderWithMessage(mockTransport);
    827 
    828         // index the message(s)
    829         setupUidlSequence(mockTransport, 2);
    830         Message[] messages = mFolder.getMessages(1, 2, null);
    831         assertEquals(2, messages.length);
    832 
    833         // basic fetch of flags & envelope
    834         setupListSequence(mockTransport, 2);
    835         FetchProfile fp = new FetchProfile();
    836         fp.add(FetchProfile.Item.FLAGS);
    837         fp.add(FetchProfile.Item.ENVELOPE);
    838         mFolder.fetch(messages, fp, null);
    839 
    840         // A side effect of how messages work is that if you get fields that are empty,
    841         // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom).  The
    842         // standard message parser needs to clear these before parsing.  Make sure that this
    843         // is happening.  (This doesn't affect IMAP, which reads the headers directly via
    844         // IMAP envelopes.)
    845         for (Message message : messages) {
    846             message.getRecipients(RecipientType.TO);
    847             message.getRecipients(RecipientType.CC);
    848             message.getRecipients(RecipientType.BCC);
    849         }
    850 
    851         // In the cases below, we fetch BODY_SANE which tries to load the first chunk of the
    852         // message (not the entire thing) in order to quickly access the headers.
    853         // In the first test, TOP succeeds
    854         Message[] singleMessage = new Message[] { messages[0] };
    855         setupSingleMessageTop(mockTransport, 1, true, true);        // try TOP & succeed
    856         fp = new FetchProfile();
    857         fp.add(FetchProfile.Item.BODY_SANE);
    858         mFolder.fetch(singleMessage, fp, null);
    859         checkFetchedMessage(singleMessage[0], 1, false);
    860 
    861         // In the 2nd test, TOP fails, so we should fall back to RETR
    862         singleMessage[0] = messages[1];
    863         setupSingleMessageTop(mockTransport, 2, true, false);        // try TOP & fail
    864         fp = new FetchProfile();
    865         fp.add(FetchProfile.Item.BODY_SANE);
    866         mFolder.fetch(singleMessage, fp, null);
    867         checkFetchedMessage(singleMessage[0], 2, false);
    868     }
    869 
    870     /**
    871      * Set up a basic MockTransport. open it, and inject it into mStore
    872      */
    873     private MockTransport openAndInjectMockTransport() {
    874         // Create mock transport and inject it into the POP3Store that's already set up
    875         MockTransport mockTransport = new MockTransport();
    876         mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
    877         mStore.setTransport(mockTransport);
    878         return mockTransport;
    879     }
    880 
    881     /**
    882      * Open a folder that's preloaded with one unread message.
    883      *
    884      * @param mockTransport the mock transport we're using
    885      */
    886     private void openFolderWithMessage(MockTransport mockTransport) throws MessagingException {
    887         // try to open it
    888         setupOpenFolder(mockTransport, 1, null);
    889         mFolder.open(OpenMode.READ_ONLY);
    890 
    891         // check message count
    892         assertEquals(1, mFolder.getMessageCount());
    893     }
    894 
    895     /**
    896      * Look at a fetched message and confirm that it is complete.
    897      *
    898      * TODO this needs to be more dynamic, not just hardcoded for empty message #1.
    899      *
    900      * @param message the fetched message to be checked
    901      * @param msgNum the message number
    902      */
    903     private void checkFetchedMessage(Message message, int msgNum, boolean body)
    904             throws MessagingException {
    905         // check To:
    906         Address[] to = message.getRecipients(RecipientType.TO);
    907         assertNotNull(to);
    908         assertEquals(1, to.length);
    909         assertEquals("Smith (at) Registry.Org", to[0].getAddress());
    910         assertNull(to[0].getPersonal());
    911 
    912         // check From:
    913         Address[] from = message.getFrom();
    914         assertNotNull(from);
    915         assertEquals(1, from.length);
    916         assertEquals("Jones (at) Registry.Org", from[0].getAddress());
    917         assertNull(from[0].getPersonal());
    918 
    919         // check Cc:
    920         Address[] cc = message.getRecipients(RecipientType.CC);
    921         assertNotNull(cc);
    922         assertEquals(1, cc.length);
    923         assertEquals("Chris (at) Registry.Org", cc[0].getAddress());
    924         assertNull(cc[0].getPersonal());
    925 
    926         // check Reply-To:
    927         Address[] replyto = message.getReplyTo();
    928         assertNotNull(replyto);
    929         assertEquals(1, replyto.length);
    930         assertEquals("Roger (at) Registry.Org", replyto[0].getAddress());
    931         assertNull(replyto[0].getPersonal());
    932 
    933         // TODO date
    934 
    935         // TODO check body (if applicable)
    936     }
    937 
    938     /**
    939      * Helper which stuffs the mock with enough strings to satisfy a call to Pop3Folder.open()
    940      *
    941      * @param mockTransport the mock transport we're using
    942      * @param statCount the number of messages to indicate in the STAT, or -1 for broken STAT
    943      * @param capabilities if non-null, comma-separated list of capabilities
    944      */
    945     private void setupOpenFolder(MockTransport mockTransport, int statCount, String capabilities) {
    946         mockTransport.expect(null, "+OK Hello there from the Mock Transport.");
    947         if (capabilities == null) {
    948             mockTransport.expect("CAPA", "-ERR unimplemented");
    949         } else {
    950             mockTransport.expect("CAPA", "+OK capabilities follow");
    951             mockTransport.expect(null, capabilities.split(","));        // one capability per line
    952             mockTransport.expect(null, ".");                            // terminated by "."
    953         }
    954         mockTransport.expect("USER user", "+OK User name accepted");
    955         mockTransport.expect("PASS password", "+OK Logged in");
    956         if (statCount == -1) {
    957             mockTransport.expect("STAT", "");
    958         } else {
    959             String stat = "+OK " + Integer.toString(statCount) + " "
    960                     + Integer.toString(PER_MESSAGE_SIZE * statCount);
    961             mockTransport.expect("STAT", stat);
    962         }
    963     }
    964 
    965     /**
    966      * Setup expects for a UIDL on a mailbox with 0 or more messages in it.
    967      * @param transport The mock transport to preload
    968      * @param numMessages The number of messages to return from UIDL.
    969      */
    970     private static void setupUidlSequence(MockTransport transport, int numMessages) {
    971         transport.expect("UIDL", "+OK sending UIDL list");
    972         for (int msgNum = 1; msgNum <= numMessages; ++msgNum) {
    973             transport.expect(null, Integer.toString(msgNum) + " " + getSingleMessageUID(msgNum));
    974         }
    975         transport.expect(null, ".");
    976     }
    977 
    978     /**
    979      * Setup expects for a LIST on a mailbox with 0 or more messages in it.
    980      * @param transport The mock transport to preload
    981      * @param numMessages The number of messages to return from LIST.
    982      */
    983     private static void setupListSequence(MockTransport transport, int numMessages) {
    984         transport.expect("LIST", "+OK sending scan listing");
    985         for (int msgNum = 1; msgNum <= numMessages; ++msgNum) {
    986             transport.expect(null, Integer.toString(msgNum) + " " +
    987                     Integer.toString(PER_MESSAGE_SIZE * msgNum));
    988         }
    989         transport.expect(null, ".");
    990     }
    991 
    992     /**
    993      * Setup expects for a LIST on a mailbox with 0 or more messages in it, except that
    994      * this time the pipe fails, and we return empty lines.
    995      * @param transport The mock transport to preload
    996      * @param numMessages The number of messages to return from LIST.
    997      */
    998     private static void setupBrokenListSequence(MockTransport transport, int numMessages) {
    999         transport.expect("LIST", "");
   1000         for (int msgNum = 1; msgNum <= numMessages; ++msgNum) {
   1001             transport.expect(null, "");
   1002         }
   1003         transport.expect(null, "");
   1004     }
   1005 
   1006     /**
   1007      * Setup a single message to be retrieved.
   1008      *
   1009      * Per RFC822 here is a minimal message header:
   1010      *     Date:     26 Aug 76 1429 EDT
   1011      *     From:     Jones (at) Registry.Org
   1012      *     To:       Smith (at) Registry.Org
   1013      *
   1014      * We'll add the following fields to support additional tests:
   1015      *     Cc:       Chris (at) Registry.Org
   1016      *     Reply-To: Roger (at) Registry.Org
   1017      *
   1018      * @param transport the mock transport to preload
   1019      * @param msgNum the message number to expect and return
   1020      * @param body if true, a non-empty body will be added
   1021      */
   1022     private static void setupSingleMessage(MockTransport transport, int msgNum, boolean body) {
   1023         setupSingleMessageTop(transport, msgNum, false, false);
   1024     }
   1025 
   1026     /**
   1027      * Setup a single message to be retrieved (headers only).
   1028      * This is very similar to setupSingleMessage() but is intended to test the BODY_SANE
   1029      * fetch mode.
   1030      * @param transport the mock transport
   1031      * @param msgNum the message number to expect and return
   1032      * @param topTry if true, the "client" is going to attempt the TOP command
   1033      * @param topSupported if true, the "server" supports the TOP command
   1034      */
   1035     private static void setupSingleMessageTop(MockTransport transport, int msgNum,
   1036             boolean topTry, boolean topSupported) {
   1037         String msgNumString = Integer.toString(msgNum);
   1038         String topCommand = "TOP " + msgNumString + " 673";
   1039         String retrCommand = "RETR " + msgNumString;
   1040 
   1041         if (topTry) {
   1042             if (topSupported) {
   1043                 transport.expect(topCommand, "+OK message follows");
   1044             } else {
   1045                 transport.expect(topCommand, "-ERR unsupported command");
   1046                 transport.expect(retrCommand, "+OK message follows");
   1047             }
   1048         } else {
   1049             transport.expect(retrCommand, "+OK message follows");
   1050         }
   1051 
   1052         transport.expect(null, "Date: 26 Aug 76 1429 EDT");
   1053         transport.expect(null, "From: Jones (at) Registry.Org");
   1054         transport.expect(null, "To:   Smith (at) Registry.Org");
   1055         transport.expect(null, "CC:   Chris (at) Registry.Org");
   1056         transport.expect(null, "Reply-To: Roger (at) Registry.Org");
   1057         transport.expect(null, "");
   1058         transport.expect(null, ".");
   1059     }
   1060 
   1061     /**
   1062      * Generates a simple unique code for each message.  Repeatable.
   1063      * @param msgNum The message number
   1064      * @return a string that can be used as the UID
   1065      */
   1066     private static String getSingleMessageUID(int msgNum) {
   1067         final String UID_HEAD = "ABCDEF-";
   1068         final String UID_TAIL = "";
   1069         return UID_HEAD + Integer.toString(msgNum) + UID_TAIL;
   1070     }
   1071 }
   1072