| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.email.mail.store; |
| |
| import android.content.Context; |
| import android.test.AndroidTestCase; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.test.suitebuilder.annotation.Suppress; |
| |
| import com.android.email.DBTestHelper; |
| import com.android.email.mail.transport.MockTransport; |
| import com.android.email.provider.ProviderTestUtils; |
| import com.android.emailcommon.TempDirectory; |
| import com.android.emailcommon.internet.MimeMessage; |
| import com.android.emailcommon.mail.Address; |
| import com.android.emailcommon.mail.FetchProfile; |
| import com.android.emailcommon.mail.Flag; |
| import com.android.emailcommon.mail.Folder; |
| import com.android.emailcommon.mail.Folder.FolderType; |
| import com.android.emailcommon.mail.Folder.OpenMode; |
| import com.android.emailcommon.mail.Message; |
| import com.android.emailcommon.mail.Message.RecipientType; |
| import com.android.emailcommon.mail.MessagingException; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.HostAuth; |
| |
| /** |
| * This is a series of unit tests for the POP3 Store class. These tests must be locally |
| * complete - no server(s) required. |
| */ |
| @Suppress |
| @SmallTest |
| public class Pop3StoreUnitTests extends AndroidTestCase { |
| final String UNIQUE_ID_1 = "20080909002219r1800rrjo9e00"; |
| |
| final static int PER_MESSAGE_SIZE = 100; |
| |
| /* These values are provided by setUp() */ |
| private Pop3Store mStore = null; |
| private Pop3Store.Pop3Folder mFolder = null; |
| |
| private Context mMockContext; |
| private HostAuth mHostAuth; |
| |
| /** |
| * Setup code. We generate a lightweight Pop3Store and Pop3Store.Pop3Folder. |
| */ |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mMockContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext( |
| getContext()); |
| |
| // Use the target's (i.e. the Email application) context |
| TempDirectory.setTempDirectory(mMockContext); |
| |
| // These are needed so we can get at the inner classes |
| mHostAuth = new HostAuth(); |
| Account testAccount = ProviderTestUtils.setupAccount("acct1", false, mMockContext); |
| |
| mHostAuth.setLogin("user", "password"); |
| mHostAuth.setConnection("pop3", "server", 999); |
| testAccount.mHostAuthRecv = mHostAuth; |
| testAccount.save(mMockContext); |
| mStore = (Pop3Store) Pop3Store.newInstance(testAccount, mMockContext); |
| mFolder = (Pop3Store.Pop3Folder) mStore.getFolder("INBOX"); |
| } |
| |
| /** |
| * Test various sunny-day operations of UIDL parser for multi-line responses |
| */ |
| public void testUIDLParserMulti() { |
| |
| // multi-line mode |
| Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser(); |
| |
| // Test basic in-list UIDL |
| parser.parseMultiLine("101 " + UNIQUE_ID_1); |
| assertEquals(101, parser.mMessageNumber); |
| assertEquals(UNIQUE_ID_1, parser.mUniqueId); |
| assertFalse(parser.mEndOfMessage); |
| assertFalse(parser.mErr); |
| |
| // Test end-of-list |
| parser.parseMultiLine("."); |
| assertTrue(parser.mEndOfMessage); |
| assertFalse(parser.mErr); |
| } |
| |
| /** |
| * Test various sunny-day operations of UIDL parser for single-line responses |
| */ |
| public void testUIDLParserSingle() { |
| |
| // single-line mode |
| Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser(); |
| |
| // Test single-message OK response |
| parser.parseSingleLine("+OK 101 " + UNIQUE_ID_1); |
| assertEquals(101, parser.mMessageNumber); |
| assertEquals(UNIQUE_ID_1, parser.mUniqueId); |
| assertTrue(parser.mEndOfMessage); |
| |
| // Test single-message ERR response |
| parser.parseSingleLine("-ERR what???"); |
| assertTrue(parser.mErr); |
| } |
| |
| /** |
| * Test various rainy-day operations of the UIDL parser for multi-line responses |
| * TODO other malformed responses |
| */ |
| public void testUIDLParserMultiFail() { |
| // multi-line mode |
| Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser(); |
| |
| // Test with null input |
| boolean result; |
| result = parser.parseMultiLine(null); |
| assertFalse(result); |
| |
| // Test with empty input |
| result = parser.parseMultiLine(""); |
| assertFalse(result); |
| } |
| |
| /** |
| * Test various rainy-day operations of the UIDL parser for single-line responses |
| * TODO other malformed responses |
| */ |
| public void testUIDLParserSingleFail() { |
| // single-line mode |
| Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser(); |
| |
| // Test with null input |
| boolean result; |
| result = parser.parseSingleLine(null); |
| assertFalse(result); |
| |
| // Test with empty input |
| result = parser.parseSingleLine(""); |
| assertFalse(result); |
| } |
| |
| /** |
| * Tests that variants on the RFC-specified formatting of UIDL work properly. |
| */ |
| public void testUIDLComcastVariant() { |
| |
| // multi-line mode |
| Pop3Store.Pop3Folder.UidlParser parser = mFolder.new UidlParser(); |
| |
| // Comcast servers send multiple spaces in their darn UIDL strings. |
| parser.parseMultiLine("101 " + UNIQUE_ID_1); |
| assertEquals(101, parser.mMessageNumber); |
| assertEquals(UNIQUE_ID_1, parser.mUniqueId); |
| assertFalse(parser.mEndOfMessage); |
| assertFalse(parser.mErr); |
| } |
| |
| /** |
| * Confirms simple non-SSL non-TLS login |
| */ |
| public void testSimpleLogin() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| // try to open it |
| setupOpenFolder(mockTransport, 0, null); |
| mFolder.open(OpenMode.READ_ONLY); |
| } |
| |
| /** |
| * TODO: Test with SSL negotiation (faked) |
| * TODO: Test with SSL required but not supported |
| * TODO: Test with TLS negotiation (faked) |
| * TODO: Test with TLS required but not supported |
| * TODO: Test calling getMessageCount(), getMessages(), etc. |
| */ |
| |
| /** |
| * Test the operation of checkSettings(), which requires (a) a good open and (b) UIDL support. |
| */ |
| public void testCheckSettings() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| // scenario 1: CAPA returns -ERR, so we try UIDL explicitly |
| setupOpenFolder(mockTransport, 0, null); |
| setupUidlSequence(mockTransport, 1); |
| mockTransport.expect("QUIT", ""); |
| mStore.checkSettings(); |
| |
| // scenario 2: CAPA indicates UIDL, so we don't try UIDL |
| setupOpenFolder(mockTransport, 0, "UIDL"); |
| mockTransport.expect("QUIT", ""); |
| mStore.checkSettings(); |
| |
| // scenario 3: CAPA returns -ERR, and UIDL fails |
| try { |
| setupOpenFolder(mockTransport, 0, null); |
| mockTransport.expect("UIDL", "-ERR unsupported"); |
| mockTransport.expect("QUIT", ""); |
| mStore.checkSettings(); |
| fail("MessagingException was expected due to UIDL unsupported."); |
| } catch (MessagingException me) { |
| // this is expected, so eat it |
| } |
| } |
| |
| /** |
| * Test a strange case that causes open to proceed without mCapabilities |
| * open - fail with "-" error code |
| * then check capabilities |
| */ |
| public void testCheckSettingsCapabilities() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| // First, preload an open that fails for some reason |
| mockTransport.expect(null, "-ERR from the Mock Transport."); |
| |
| // And watch it fail |
| try { |
| Pop3Store.Pop3Folder folder = mStore.new Pop3Folder("INBOX"); |
| folder.open(OpenMode.READ_WRITE); |
| fail("Should have thrown exception"); |
| } catch (MessagingException me) { |
| // Expected - continue. |
| } |
| |
| // Now try again (assuming a slightly different connection setup - successful) |
| // Note, checkSettings is going to try to close the connection again, so we expect |
| // one extra QUIT before we spin it up again |
| mockTransport.expect("QUIT", ""); |
| mockTransport.expectClose(); |
| setupOpenFolder(mockTransport, 0, "UIDL"); |
| mockTransport.expect("QUIT", ""); |
| mStore.checkSettings(); |
| } |
| |
| /** |
| * Test small Store & Folder functions that manage folders & namespace |
| */ |
| public void testStoreFoldersFunctions() { |
| |
| // getPersonalNamespaces() always returns INBOX folder |
| Folder[] folders = mStore.updateFolders(); |
| assertEquals(1, folders.length); |
| assertSame(mFolder, folders[0]); |
| |
| // getName() returns the name we were created with. If "inbox", converts to INBOX |
| assertEquals("INBOX", mFolder.getName()); |
| Pop3Store.Pop3Folder folderMixedCaseInbox = mStore.new Pop3Folder("iNbOx"); |
| assertEquals("INBOX", folderMixedCaseInbox.getName()); |
| Pop3Store.Pop3Folder folderNotInbox = mStore.new Pop3Folder("NOT-INBOX"); |
| assertEquals("NOT-INBOX", folderNotInbox.getName()); |
| |
| // exists() true if name is INBOX |
| assertTrue(mFolder.exists()); |
| assertTrue(folderMixedCaseInbox.exists()); |
| assertFalse(folderNotInbox.exists()); |
| } |
| |
| /** |
| * Test small Folder functions that don't really do anything in Pop3 |
| */ |
| public void testSmallFolderFunctions() { |
| |
| // getMode() returns OpenMode.READ_WRITE |
| assertEquals(OpenMode.READ_WRITE, mFolder.getMode()); |
| |
| // canCreate() && create() return false |
| assertFalse(mFolder.canCreate(FolderType.HOLDS_FOLDERS)); |
| assertFalse(mFolder.canCreate(FolderType.HOLDS_MESSAGES)); |
| assertFalse(mFolder.create(FolderType.HOLDS_FOLDERS)); |
| assertFalse(mFolder.create(FolderType.HOLDS_MESSAGES)); |
| |
| // getUnreadMessageCount() always returns -1 |
| assertEquals(-1, mFolder.getUnreadMessageCount()); |
| |
| // getPermanentFlags() returns { Flag.DELETED } |
| Flag[] flags = mFolder.getPermanentFlags(); |
| assertEquals(1, flags.length); |
| assertEquals(Flag.DELETED, flags[0]); |
| |
| // appendMessages(Message[] messages) does nothing |
| mFolder.appendMessages(null); |
| |
| // delete(boolean recurse) does nothing |
| // TODO - it should! |
| mFolder.delete(false); |
| |
| // expunge() returns null |
| assertNull(mFolder.expunge()); |
| |
| // copyMessages() is unsupported |
| try { |
| mFolder.copyMessages(null, null, null); |
| fail("Exception not thrown by copyMessages()"); |
| } catch (UnsupportedOperationException e) { |
| // expected - succeed |
| } |
| } |
| |
| /** |
| * Lightweight test to confirm that POP3 hasn't implemented any folder roles yet. |
| */ |
| public void testNoFolderRolesYet() { |
| Folder[] remoteFolders = mStore.updateFolders(); |
| for (Folder folder : remoteFolders) { |
| assertEquals(Folder.FolderRole.UNKNOWN, folder.getRole()); |
| } |
| } |
| |
| /** |
| * Lightweight test to confirm that POP3 is requesting sent-message-upload. |
| */ |
| public void testSentUploadRequested() { |
| assertTrue(mStore.requireCopyMessageToSentFolder()); |
| } |
| |
| /** |
| * Test the process of opening and indexing a mailbox with one unread message in it. |
| * |
| * TODO should create an instrumented listener to confirm all expected callbacks. Then use |
| * it everywhere we could have passed a message listener. |
| */ |
| public void testOneUnread() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the process of opening and getting message by uid. |
| */ |
| public void testGetMessageByUid() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| setupOpenFolder(mockTransport, 2, null); |
| mFolder.open(OpenMode.READ_WRITE); |
| // check message count |
| assertEquals(2, mFolder.getMessageCount()); |
| |
| // setup 2 messages |
| setupUidlSequence(mockTransport, 2); |
| String uid1 = getSingleMessageUID(1); |
| String uid2 = getSingleMessageUID(2); |
| String uid3 = getSingleMessageUID(3); |
| |
| Message msg1 = mFolder.getMessage(uid1); |
| assertTrue("message with uid1", msg1 != null); |
| |
| // uid3 does not exist |
| Message msg3 = mFolder.getMessage(uid3); |
| assertTrue("message with uid3", msg3 == null); |
| |
| Message msg2 = mFolder.getMessage(uid2); |
| assertTrue("message with uid2", msg2 != null); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we are simulating the steps of |
| * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit |
| * further along in each case, to test various recovery points. |
| * |
| * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in |
| * Pop3Folder.getMessages(), due to a closure before the UIDL command completes. |
| */ |
| public void testCatchClosed1a() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // cause the next sequence to fail on the readLine() calls |
| mockTransport.closeInputStream(); |
| |
| // index the message(s) - it should fail, because our stream is broken |
| try { |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| fail("Broken stream should cause getMessages() to throw."); |
| } catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we are simulating the steps of |
| * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit |
| * further along in each case, to test various recovery points. |
| * |
| * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in |
| * Pop3Folder.getMessages(), due to non-numeric data in a multi-line UIDL. |
| */ |
| public void testCatchClosed1b() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) - it should fail, because our stream is broken |
| try { |
| // setupUidlSequence(mockTransport, 1); |
| mockTransport.expect("UIDL", "+OK sending UIDL list"); |
| mockTransport.expect(null, "bad-data" + " " + "THE-UIDL"); |
| mockTransport.expect(null, "."); |
| |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| fail("Bad UIDL should cause getMessages() to throw."); |
| } catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we are simulating the steps of |
| * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit |
| * further along in each case, to test various recovery points. |
| * |
| * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in |
| * Pop3Folder.getMessages(), due to non-numeric data in a single-line UIDL. |
| */ |
| public void testCatchClosed1c() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| // openFolderWithMessage(mockTransport); |
| setupOpenFolder(mockTransport, 6000, null); |
| mFolder.open(OpenMode.READ_ONLY); |
| assertEquals(6000, mFolder.getMessageCount()); |
| |
| // index the message(s) - it should fail, because our stream is broken |
| try { |
| // setupUidlSequence(mockTransport, 1); |
| mockTransport.expect("UIDL 1", "+OK " + "bad-data" + " " + "THE-UIDL"); |
| |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| fail("Bad UIDL should cause getMessages() to throw."); |
| } catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we are simulating the steps of |
| * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit |
| * further along in each case, to test various recovery points. |
| * |
| * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in |
| * Pop3Folder.fetch(), for a failure in the call to indexUids(). |
| */ |
| public void testCatchClosed2() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| |
| // cause the next sequence to fail on the readLine() calls |
| mockTransport.closeInputStream(); |
| |
| try { |
| // try the basic fetch of flags & envelope |
| setupListSequence(mockTransport, 1); |
| FetchProfile fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.FLAGS); |
| fp.add(FetchProfile.Item.ENVELOPE); |
| mFolder.fetch(messages, fp, null); |
| assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); |
| fail("Broken stream should cause fetch() to throw."); |
| } |
| catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we have to check additional places where |
| * Pop3Store and/or Pop3Folder should be dealing with IOErrors. |
| * |
| * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in |
| * Pop3Folder.fetch(), for a failure in the call to fetchEnvelope(). |
| */ |
| public void testCatchClosed2a() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| |
| // try the basic fetch of flags & envelope, but the LIST command fails |
| setupBrokenListSequence(mockTransport, 1); |
| try { |
| FetchProfile fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.FLAGS); |
| fp.add(FetchProfile.Item.ENVELOPE); |
| mFolder.fetch(messages, fp, null); |
| assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); |
| fail("Broken stream should cause fetch() to throw."); |
| } catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we are simulating the steps of |
| * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit |
| * further along in each case, to test various recovery points. |
| * |
| * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in |
| * Pop3Folder.fetch(). |
| */ |
| public void testCatchClosed3() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| |
| // try the basic fetch of flags & envelope |
| setupListSequence(mockTransport, 1); |
| FetchProfile fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.FLAGS); |
| fp.add(FetchProfile.Item.ENVELOPE); |
| mFolder.fetch(messages, fp, null); |
| assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); |
| |
| // cause the next sequence to fail on the readLine() calls |
| mockTransport.closeInputStream(); |
| |
| try { |
| // now try fetching the message |
| setupSingleMessage(mockTransport, 1, false); |
| fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.BODY); |
| mFolder.fetch(messages, fp, null); |
| checkFetchedMessage(messages[0], 1, false); |
| fail("Broken stream should cause fetch() to throw."); |
| } |
| catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we have to check additional places where |
| * Pop3Store and/or Pop3Folder should be dealing with IOErrors. |
| * |
| * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in |
| * Pop3Folder.setFlags(). |
| */ |
| public void testCatchClosed4() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| |
| // cause the next sequence to fail on the readLine() calls |
| mockTransport.closeInputStream(); |
| |
| // delete 'em all - should fail because of broken stream |
| try { |
| mockTransport.expect("DELE 1", "+OK message deleted"); |
| mFolder.setFlags(messages, new Flag[] { Flag.DELETED }, true); |
| fail("Broken stream should cause fetch() to throw."); |
| } |
| catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll just repeat the above steps, without the failure. |
| // NOTE: everything from here down is copied from testOneUnread() and should be consolidated |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we have to check additional places where |
| * Pop3Store and/or Pop3Folder should be dealing with IOErrors. |
| * |
| * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in |
| * Pop3Folder.open(). |
| */ |
| public void testCatchClosed5() { |
| // TODO cannot write this test until we can inject stream closures mid-sequence |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we have to check additional places where |
| * Pop3Store and/or Pop3Folder should be dealing with IOErrors. |
| * |
| * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in |
| * Pop3Folder.open() (when it calls STAT and the response is empty of garbagey). |
| */ |
| public void testCatchClosed6a() throws MessagingException { |
| |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| |
| // like openFolderWithMessage(mockTransport) but with a broken STAT report (empty response) |
| setupOpenFolder(mockTransport, -1, null); |
| try { |
| mFolder.open(OpenMode.READ_ONLY); |
| fail("Broken STAT should cause open() to throw."); |
| } catch(MessagingException me) { |
| // success |
| } |
| |
| // At this point the UI would display connection error, which is fine. Now, the real |
| // test is, can we recover? So I'll try a new connection, without the failure. |
| |
| // confirm that we're closed at this point |
| assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); |
| |
| // and confirm that the next connection will be OK |
| checkOneUnread(mockTransport); |
| } |
| |
| /** |
| * Test the scenario where the transport is "open" but not really (e.g. server closed). Two |
| * things should happen: We should see an intermediate failure that makes sense, and the next |
| * operation should reopen properly. |
| * |
| * There are multiple versions of this test because we have to check additional places where |
| * Pop3Store and/or Pop3Folder should be dealing with IOErrors. |
| * |
| * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in |
| * Pop3Folder.open() (when it calls STAT, and there is no response at all). |
| */ |
| public void testCatchClosed6b() { |
| // TODO cannot write this test until we can inject stream closures mid-sequence |
| } |
| |
| /** |
| * Given an initialized mock transport, open it and attempt to "read" one unread message from |
| * it. This can be used as a basic test of functionality and it should be possible to call this |
| * repeatedly (if you close the folder between calls). |
| * |
| * @param mockTransport the mock transport we're using |
| */ |
| private void checkOneUnread(MockTransport mockTransport) throws MessagingException { |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 1); |
| Message[] messages = mFolder.getMessages(1, 1, null); |
| assertEquals(1, messages.length); |
| assertEquals(getSingleMessageUID(1), messages[0].getUid()); |
| |
| // try the basic fetch of flags & envelope |
| setupListSequence(mockTransport, 1); |
| FetchProfile fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.FLAGS); |
| fp.add(FetchProfile.Item.ENVELOPE); |
| mFolder.fetch(messages, fp, null); |
| assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); |
| |
| // A side effect of how messages work is that if you get fields that are empty, |
| // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom). The |
| // standard message parser needs to clear these before parsing. Make sure that this |
| // is happening. (This doesn't affect IMAP, which reads the headers directly via |
| // IMAP evelopes.) |
| MimeMessage message = (MimeMessage) messages[0]; |
| message.getRecipients(RecipientType.TO); |
| message.getRecipients(RecipientType.CC); |
| message.getRecipients(RecipientType.BCC); |
| |
| // now try fetching the message |
| setupSingleMessage(mockTransport, 1, false); |
| fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.BODY); |
| mFolder.fetch(messages, fp, null); |
| checkFetchedMessage(messages[0], 1, false); |
| } |
| |
| /** |
| * A group of tests to confirm that we're properly juggling the RETR and TOP commands. |
| * Some servers (hello, live.com) support TOP but don't support CAPA. So we ignore CAPA |
| * and just try TOP. |
| */ |
| public void testRetrVariants() throws MessagingException { |
| MockTransport mockTransport = openAndInjectMockTransport(); |
| openFolderWithMessage(mockTransport); |
| |
| // index the message(s) |
| setupUidlSequence(mockTransport, 2); |
| Message[] messages = mFolder.getMessages(1, 2, null); |
| assertEquals(2, messages.length); |
| |
| // basic fetch of flags & envelope |
| setupListSequence(mockTransport, 2); |
| FetchProfile fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.FLAGS); |
| fp.add(FetchProfile.Item.ENVELOPE); |
| mFolder.fetch(messages, fp, null); |
| |
| // A side effect of how messages work is that if you get fields that are empty, |
| // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom). The |
| // standard message parser needs to clear these before parsing. Make sure that this |
| // is happening. (This doesn't affect IMAP, which reads the headers directly via |
| // IMAP envelopes.) |
| for (Message message : messages) { |
| message.getRecipients(RecipientType.TO); |
| message.getRecipients(RecipientType.CC); |
| message.getRecipients(RecipientType.BCC); |
| } |
| |
| // In the cases below, we fetch BODY_SANE which tries to load the first chunk of the |
| // message (not the entire thing) in order to quickly access the headers. |
| // In the first test, TOP succeeds |
| Message[] singleMessage = new Message[] { messages[0] }; |
| setupSingleMessageTop(mockTransport, 1, true, true); // try TOP & succeed |
| fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.BODY_SANE); |
| mFolder.fetch(singleMessage, fp, null); |
| checkFetchedMessage(singleMessage[0], 1, false); |
| |
| // In the 2nd test, TOP fails, so we should fall back to RETR |
| singleMessage[0] = messages[1]; |
| setupSingleMessageTop(mockTransport, 2, true, false); // try TOP & fail |
| fp = new FetchProfile(); |
| fp.add(FetchProfile.Item.BODY_SANE); |
| mFolder.fetch(singleMessage, fp, null); |
| checkFetchedMessage(singleMessage[0], 2, false); |
| } |
| |
| /** |
| * Set up a basic MockTransport. open it, and inject it into mStore |
| */ |
| private MockTransport openAndInjectMockTransport() { |
| // Create mock transport and inject it into the POP3Store that's already set up |
| MockTransport mockTransport = new MockTransport(mContext, mHostAuth); |
| mockTransport.setSecurity(HostAuth.FLAG_NONE, false); |
| mStore.setTransport(mockTransport); |
| return mockTransport; |
| } |
| |
| /** |
| * Open a folder that's preloaded with one unread message. |
| * |
| * @param mockTransport the mock transport we're using |
| */ |
| private void openFolderWithMessage(MockTransport mockTransport) throws MessagingException { |
| // try to open it |
| setupOpenFolder(mockTransport, 1, null); |
| mFolder.open(OpenMode.READ_ONLY); |
| |
| // check message count |
| assertEquals(1, mFolder.getMessageCount()); |
| } |
| |
| /** |
| * Look at a fetched message and confirm that it is complete. |
| * |
| * TODO this needs to be more dynamic, not just hardcoded for empty message #1. |
| * |
| * @param message the fetched message to be checked |
| * @param msgNum the message number |
| */ |
| private void checkFetchedMessage(Message message, int msgNum, boolean body) |
| throws MessagingException { |
| // check To: |
| Address[] to = message.getRecipients(RecipientType.TO); |
| assertNotNull(to); |
| assertEquals(1, to.length); |
| assertEquals("Smith@Registry.Org", to[0].getAddress()); |
| assertNull(to[0].getPersonal()); |
| |
| // check From: |
| Address[] from = message.getFrom(); |
| assertNotNull(from); |
| assertEquals(1, from.length); |
| assertEquals("Jones@Registry.Org", from[0].getAddress()); |
| assertNull(from[0].getPersonal()); |
| |
| // check Cc: |
| Address[] cc = message.getRecipients(RecipientType.CC); |
| assertNotNull(cc); |
| assertEquals(1, cc.length); |
| assertEquals("Chris@Registry.Org", cc[0].getAddress()); |
| assertNull(cc[0].getPersonal()); |
| |
| // check Reply-To: |
| Address[] replyto = message.getReplyTo(); |
| assertNotNull(replyto); |
| assertEquals(1, replyto.length); |
| assertEquals("Roger@Registry.Org", replyto[0].getAddress()); |
| assertNull(replyto[0].getPersonal()); |
| |
| // TODO date |
| |
| // TODO check body (if applicable) |
| } |
| |
| /** |
| * Helper which stuffs the mock with enough strings to satisfy a call to Pop3Folder.open() |
| * |
| * @param mockTransport the mock transport we're using |
| * @param statCount the number of messages to indicate in the STAT, or -1 for broken STAT |
| * @param capabilities if non-null, comma-separated list of capabilities |
| */ |
| private void setupOpenFolder(MockTransport mockTransport, int statCount, String capabilities) { |
| mockTransport.expect(null, "+OK Hello there from the Mock Transport."); |
| if (capabilities == null) { |
| mockTransport.expect("CAPA", "-ERR unimplemented"); |
| } else { |
| mockTransport.expect("CAPA", "+OK capabilities follow"); |
| mockTransport.expect(null, capabilities.split(",")); // one capability per line |
| mockTransport.expect(null, "."); // terminated by "." |
| } |
| mockTransport.expect("USER user", "+OK User name accepted"); |
| mockTransport.expect("PASS password", "+OK Logged in"); |
| if (statCount == -1) { |
| mockTransport.expect("STAT", ""); |
| } else { |
| String stat = "+OK " + Integer.toString(statCount) + " " |
| + Integer.toString(PER_MESSAGE_SIZE * statCount); |
| mockTransport.expect("STAT", stat); |
| } |
| } |
| |
| /** |
| * Setup expects for a UIDL on a mailbox with 0 or more messages in it. |
| * @param transport The mock transport to preload |
| * @param numMessages The number of messages to return from UIDL. |
| */ |
| private static void setupUidlSequence(MockTransport transport, int numMessages) { |
| transport.expect("UIDL", "+OK sending UIDL list"); |
| for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { |
| transport.expect(null, Integer.toString(msgNum) + " " + getSingleMessageUID(msgNum)); |
| } |
| transport.expect(null, "."); |
| } |
| |
| /** |
| * Setup expects for a LIST on a mailbox with 0 or more messages in it. |
| * @param transport The mock transport to preload |
| * @param numMessages The number of messages to return from LIST. |
| */ |
| private static void setupListSequence(MockTransport transport, int numMessages) { |
| transport.expect("LIST", "+OK sending scan listing"); |
| for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { |
| transport.expect(null, Integer.toString(msgNum) + " " + |
| Integer.toString(PER_MESSAGE_SIZE * msgNum)); |
| } |
| transport.expect(null, "."); |
| } |
| |
| /** |
| * Setup expects for a LIST on a mailbox with 0 or more messages in it, except that |
| * this time the pipe fails, and we return empty lines. |
| * @param transport The mock transport to preload |
| * @param numMessages The number of messages to return from LIST. |
| */ |
| private static void setupBrokenListSequence(MockTransport transport, int numMessages) { |
| transport.expect("LIST", ""); |
| for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { |
| transport.expect(null, ""); |
| } |
| transport.expect(null, ""); |
| } |
| |
| /** |
| * Setup a single message to be retrieved. |
| * |
| * Per RFC822 here is a minimal message header: |
| * Date: 26 Aug 76 1429 EDT |
| * From: Jones@Registry.Org |
| * To: Smith@Registry.Org |
| * |
| * We'll add the following fields to support additional tests: |
| * Cc: Chris@Registry.Org |
| * Reply-To: Roger@Registry.Org |
| * |
| * @param transport the mock transport to preload |
| * @param msgNum the message number to expect and return |
| * @param body if true, a non-empty body will be added |
| */ |
| private static void setupSingleMessage(MockTransport transport, int msgNum, boolean body) { |
| setupSingleMessageTop(transport, msgNum, false, false); |
| } |
| |
| /** |
| * Setup a single message to be retrieved (headers only). |
| * This is very similar to setupSingleMessage() but is intended to test the BODY_SANE |
| * fetch mode. |
| * @param transport the mock transport |
| * @param msgNum the message number to expect and return |
| * @param topTry if true, the "client" is going to attempt the TOP command |
| * @param topSupported if true, the "server" supports the TOP command |
| */ |
| private static void setupSingleMessageTop(MockTransport transport, int msgNum, |
| boolean topTry, boolean topSupported) { |
| String msgNumString = Integer.toString(msgNum); |
| String topCommand = "TOP " + msgNumString + " 673"; |
| String retrCommand = "RETR " + msgNumString; |
| |
| if (topTry) { |
| if (topSupported) { |
| transport.expect(topCommand, "+OK message follows"); |
| } else { |
| transport.expect(topCommand, "-ERR unsupported command"); |
| transport.expect(retrCommand, "+OK message follows"); |
| } |
| } else { |
| transport.expect(retrCommand, "+OK message follows"); |
| } |
| |
| transport.expect(null, "Date: 26 Aug 76 1429 EDT"); |
| transport.expect(null, "From: Jones@Registry.Org"); |
| transport.expect(null, "To: Smith@Registry.Org"); |
| transport.expect(null, "CC: Chris@Registry.Org"); |
| transport.expect(null, "Reply-To: Roger@Registry.Org"); |
| transport.expect(null, ""); |
| transport.expect(null, "."); |
| } |
| |
| /** |
| * Generates a simple unique code for each message. Repeatable. |
| * @param msgNum The message number |
| * @return a string that can be used as the UID |
| */ |
| private static String getSingleMessageUID(int msgNum) { |
| final String UID_HEAD = "ABCDEF-"; |
| final String UID_TAIL = ""; |
| return UID_HEAD + Integer.toString(msgNum) + UID_TAIL; |
| } |
| } |