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