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
17package com.android.email.mail.store;
18
19import android.content.Context;
20import android.test.AndroidTestCase;
21import android.test.suitebuilder.annotation.SmallTest;
22import android.test.suitebuilder.annotation.Suppress;
23
24import com.android.email.DBTestHelper;
25import com.android.email.mail.transport.MockTransport;
26import com.android.email.provider.ProviderTestUtils;
27import com.android.emailcommon.TempDirectory;
28import com.android.emailcommon.internet.MimeMessage;
29import com.android.emailcommon.mail.Address;
30import com.android.emailcommon.mail.FetchProfile;
31import com.android.emailcommon.mail.Flag;
32import com.android.emailcommon.mail.Folder;
33import com.android.emailcommon.mail.Folder.FolderType;
34import com.android.emailcommon.mail.Folder.OpenMode;
35import com.android.emailcommon.mail.Message;
36import com.android.emailcommon.mail.Message.RecipientType;
37import com.android.emailcommon.mail.MessagingException;
38import com.android.emailcommon.provider.Account;
39import 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
47public 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@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@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@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@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@Registry.Org
1008     *     To:       Smith@Registry.Org
1009     *
1010     * We'll add the following fields to support additional tests:
1011     *     Cc:       Chris@Registry.Org
1012     *     Reply-To: Roger@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@Registry.Org");
1050        transport.expect(null, "To:   Smith@Registry.Org");
1051        transport.expect(null, "CC:   Chris@Registry.Org");
1052        transport.expect(null, "Reply-To: Roger@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