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 // appendMessages(Message[] messages) does nothing 302 mFolder.appendMessages(null); 303 304 // delete(boolean recurse) does nothing 305 // TODO - it should! 306 mFolder.delete(false); 307 308 // expunge() returns null 309 assertNull(mFolder.expunge()); 310 311 // copyMessages() is unsupported 312 try { 313 mFolder.copyMessages(null, null, null); 314 fail("Exception not thrown by copyMessages()"); 315 } catch (UnsupportedOperationException e) { 316 // expected - succeed 317 } 318 } 319 320 /** 321 * Lightweight test to confirm that POP3 hasn't implemented any folder roles yet. 322 */ 323 public void testNoFolderRolesYet() { 324 Folder[] remoteFolders = mStore.updateFolders(); 325 for (Folder folder : remoteFolders) { 326 assertEquals(Folder.FolderRole.UNKNOWN, folder.getRole()); 327 } 328 } 329 330 /** 331 * Lightweight test to confirm that POP3 is requesting sent-message-upload. 332 */ 333 public void testSentUploadRequested() { 334 assertTrue(mStore.requireCopyMessageToSentFolder()); 335 } 336 337 /** 338 * Test the process of opening and indexing a mailbox with one unread message in it. 339 * 340 * TODO should create an instrumented listener to confirm all expected callbacks. Then use 341 * it everywhere we could have passed a message listener. 342 */ 343 public void testOneUnread() throws MessagingException { 344 345 MockTransport mockTransport = openAndInjectMockTransport(); 346 347 checkOneUnread(mockTransport); 348 } 349 350 /** 351 * Test the process of opening and getting message by uid. 352 */ 353 public void testGetMessageByUid() throws MessagingException { 354 355 MockTransport mockTransport = openAndInjectMockTransport(); 356 357 setupOpenFolder(mockTransport, 2, null); 358 mFolder.open(OpenMode.READ_WRITE); 359 // check message count 360 assertEquals(2, mFolder.getMessageCount()); 361 362 // setup 2 messages 363 setupUidlSequence(mockTransport, 2); 364 String uid1 = getSingleMessageUID(1); 365 String uid2 = getSingleMessageUID(2); 366 String uid3 = getSingleMessageUID(3); 367 368 Message msg1 = mFolder.getMessage(uid1); 369 assertTrue("message with uid1", msg1 != null); 370 371 // uid3 does not exist 372 Message msg3 = mFolder.getMessage(uid3); 373 assertTrue("message with uid3", msg3 == null); 374 375 Message msg2 = mFolder.getMessage(uid2); 376 assertTrue("message with uid2", msg2 != null); 377 } 378 379 /** 380 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 381 * things should happen: We should see an intermediate failure that makes sense, and the next 382 * operation should reopen properly. 383 * 384 * There are multiple versions of this test because we are simulating the steps of 385 * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit 386 * further along in each case, to test various recovery points. 387 * 388 * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in 389 * Pop3Folder.getMessages(), due to a closure before the UIDL command completes. 390 */ 391 public void testCatchClosed1a() throws MessagingException { 392 393 MockTransport mockTransport = openAndInjectMockTransport(); 394 395 openFolderWithMessage(mockTransport); 396 397 // cause the next sequence to fail on the readLine() calls 398 mockTransport.closeInputStream(); 399 400 // index the message(s) - it should fail, because our stream is broken 401 try { 402 setupUidlSequence(mockTransport, 1); 403 Message[] messages = mFolder.getMessages(1, 1, null); 404 assertEquals(1, messages.length); 405 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 406 fail("Broken stream should cause getMessages() to throw."); 407 } catch(MessagingException me) { 408 // success 409 } 410 411 // At this point the UI would display connection error, which is fine. Now, the real 412 // test is, can we recover? So I'll just repeat the above steps, without the failure. 413 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 414 415 // confirm that we're closed at this point 416 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 417 418 // and confirm that the next connection will be OK 419 checkOneUnread(mockTransport); 420 } 421 422 /** 423 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 424 * things should happen: We should see an intermediate failure that makes sense, and the next 425 * operation should reopen properly. 426 * 427 * There are multiple versions of this test because we are simulating the steps of 428 * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit 429 * further along in each case, to test various recovery points. 430 * 431 * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in 432 * Pop3Folder.getMessages(), due to non-numeric data in a multi-line UIDL. 433 */ 434 public void testCatchClosed1b() throws MessagingException { 435 436 MockTransport mockTransport = openAndInjectMockTransport(); 437 438 openFolderWithMessage(mockTransport); 439 440 // index the message(s) - it should fail, because our stream is broken 441 try { 442 // setupUidlSequence(mockTransport, 1); 443 mockTransport.expect("UIDL", "+OK sending UIDL list"); 444 mockTransport.expect(null, "bad-data" + " " + "THE-UIDL"); 445 mockTransport.expect(null, "."); 446 447 Message[] messages = mFolder.getMessages(1, 1, null); 448 fail("Bad UIDL should cause getMessages() to throw."); 449 } catch(MessagingException me) { 450 // success 451 } 452 453 // At this point the UI would display connection error, which is fine. Now, the real 454 // test is, can we recover? So I'll just repeat the above steps, without the failure. 455 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 456 457 // confirm that we're closed at this point 458 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 459 460 // and confirm that the next connection will be OK 461 checkOneUnread(mockTransport); 462 } 463 464 /** 465 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 466 * things should happen: We should see an intermediate failure that makes sense, and the next 467 * operation should reopen properly. 468 * 469 * There are multiple versions of this test because we are simulating the steps of 470 * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit 471 * further along in each case, to test various recovery points. 472 * 473 * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in 474 * Pop3Folder.getMessages(), due to non-numeric data in a single-line UIDL. 475 */ 476 public void testCatchClosed1c() throws MessagingException { 477 478 MockTransport mockTransport = openAndInjectMockTransport(); 479 480 // openFolderWithMessage(mockTransport); 481 setupOpenFolder(mockTransport, 6000, null); 482 mFolder.open(OpenMode.READ_ONLY); 483 assertEquals(6000, mFolder.getMessageCount()); 484 485 // index the message(s) - it should fail, because our stream is broken 486 try { 487 // setupUidlSequence(mockTransport, 1); 488 mockTransport.expect("UIDL 1", "+OK " + "bad-data" + " " + "THE-UIDL"); 489 490 Message[] messages = mFolder.getMessages(1, 1, null); 491 fail("Bad UIDL should cause getMessages() to throw."); 492 } catch(MessagingException me) { 493 // success 494 } 495 496 // At this point the UI would display connection error, which is fine. Now, the real 497 // test is, can we recover? So I'll just repeat the above steps, without the failure. 498 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 499 500 // confirm that we're closed at this point 501 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 502 503 // and confirm that the next connection will be OK 504 checkOneUnread(mockTransport); 505 } 506 507 /** 508 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 509 * things should happen: We should see an intermediate failure that makes sense, and the next 510 * operation should reopen properly. 511 * 512 * There are multiple versions of this test because we are simulating the steps of 513 * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit 514 * further along in each case, to test various recovery points. 515 * 516 * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in 517 * Pop3Folder.fetch(), for a failure in the call to indexUids(). 518 */ 519 public void testCatchClosed2() throws MessagingException { 520 521 MockTransport mockTransport = openAndInjectMockTransport(); 522 523 openFolderWithMessage(mockTransport); 524 525 // index the message(s) 526 setupUidlSequence(mockTransport, 1); 527 Message[] messages = mFolder.getMessages(1, 1, null); 528 assertEquals(1, messages.length); 529 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 530 531 // cause the next sequence to fail on the readLine() calls 532 mockTransport.closeInputStream(); 533 534 try { 535 // try the basic fetch of flags & envelope 536 setupListSequence(mockTransport, 1); 537 FetchProfile fp = new FetchProfile(); 538 fp.add(FetchProfile.Item.FLAGS); 539 fp.add(FetchProfile.Item.ENVELOPE); 540 mFolder.fetch(messages, fp, null); 541 assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); 542 fail("Broken stream should cause fetch() to throw."); 543 } 544 catch(MessagingException me) { 545 // success 546 } 547 548 // At this point the UI would display connection error, which is fine. Now, the real 549 // test is, can we recover? So I'll just repeat the above steps, without the failure. 550 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 551 552 // confirm that we're closed at this point 553 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 554 555 // and confirm that the next connection will be OK 556 checkOneUnread(mockTransport); 557 } 558 559 /** 560 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 561 * things should happen: We should see an intermediate failure that makes sense, and the next 562 * operation should reopen properly. 563 * 564 * There are multiple versions of this test because we have to check additional places where 565 * Pop3Store and/or Pop3Folder should be dealing with IOErrors. 566 * 567 * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in 568 * Pop3Folder.fetch(), for a failure in the call to fetchEnvelope(). 569 */ 570 public void testCatchClosed2a() throws MessagingException { 571 572 MockTransport mockTransport = openAndInjectMockTransport(); 573 574 openFolderWithMessage(mockTransport); 575 576 // index the message(s) 577 setupUidlSequence(mockTransport, 1); 578 Message[] messages = mFolder.getMessages(1, 1, null); 579 assertEquals(1, messages.length); 580 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 581 582 // try the basic fetch of flags & envelope, but the LIST command fails 583 setupBrokenListSequence(mockTransport, 1); 584 try { 585 FetchProfile fp = new FetchProfile(); 586 fp.add(FetchProfile.Item.FLAGS); 587 fp.add(FetchProfile.Item.ENVELOPE); 588 mFolder.fetch(messages, fp, null); 589 assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); 590 fail("Broken stream should cause fetch() to throw."); 591 } catch(MessagingException me) { 592 // success 593 } 594 595 // At this point the UI would display connection error, which is fine. Now, the real 596 // test is, can we recover? So I'll just repeat the above steps, without the failure. 597 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 598 599 // confirm that we're closed at this point 600 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 601 602 // and confirm that the next connection will be OK 603 checkOneUnread(mockTransport); 604 } 605 606 /** 607 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 608 * things should happen: We should see an intermediate failure that makes sense, and the next 609 * operation should reopen properly. 610 * 611 * There are multiple versions of this test because we are simulating the steps of 612 * MessagingController.synchronizeMailboxSyncronous() and we will inject the failure a bit 613 * further along in each case, to test various recovery points. 614 * 615 * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in 616 * Pop3Folder.fetch(). 617 */ 618 public void testCatchClosed3() throws MessagingException { 619 620 MockTransport mockTransport = openAndInjectMockTransport(); 621 622 openFolderWithMessage(mockTransport); 623 624 // index the message(s) 625 setupUidlSequence(mockTransport, 1); 626 Message[] messages = mFolder.getMessages(1, 1, null); 627 assertEquals(1, messages.length); 628 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 629 630 // try the basic fetch of flags & envelope 631 setupListSequence(mockTransport, 1); 632 FetchProfile fp = new FetchProfile(); 633 fp.add(FetchProfile.Item.FLAGS); 634 fp.add(FetchProfile.Item.ENVELOPE); 635 mFolder.fetch(messages, fp, null); 636 assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); 637 638 // cause the next sequence to fail on the readLine() calls 639 mockTransport.closeInputStream(); 640 641 try { 642 // now try fetching the message 643 setupSingleMessage(mockTransport, 1, false); 644 fp = new FetchProfile(); 645 fp.add(FetchProfile.Item.BODY); 646 mFolder.fetch(messages, fp, null); 647 checkFetchedMessage(messages[0], 1, false); 648 fail("Broken stream should cause fetch() to throw."); 649 } 650 catch(MessagingException me) { 651 // success 652 } 653 654 // At this point the UI would display connection error, which is fine. Now, the real 655 // test is, can we recover? So I'll just repeat the above steps, without the failure. 656 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 657 658 // confirm that we're closed at this point 659 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 660 661 // and confirm that the next connection will be OK 662 checkOneUnread(mockTransport); 663 } 664 665 /** 666 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 667 * things should happen: We should see an intermediate failure that makes sense, and the next 668 * operation should reopen properly. 669 * 670 * There are multiple versions of this test because we have to check additional places where 671 * Pop3Store and/or Pop3Folder should be dealing with IOErrors. 672 * 673 * This test confirms that Pop3Store needs to call close() in the IOExceptionHandler in 674 * Pop3Folder.setFlags(). 675 */ 676 public void testCatchClosed4() throws MessagingException { 677 678 MockTransport mockTransport = openAndInjectMockTransport(); 679 680 openFolderWithMessage(mockTransport); 681 682 // index the message(s) 683 setupUidlSequence(mockTransport, 1); 684 Message[] messages = mFolder.getMessages(1, 1, null); 685 assertEquals(1, messages.length); 686 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 687 688 // cause the next sequence to fail on the readLine() calls 689 mockTransport.closeInputStream(); 690 691 // delete 'em all - should fail because of broken stream 692 try { 693 mockTransport.expect("DELE 1", "+OK message deleted"); 694 mFolder.setFlags(messages, new Flag[] { Flag.DELETED }, true); 695 fail("Broken stream should cause fetch() to throw."); 696 } 697 catch(MessagingException me) { 698 // success 699 } 700 701 // At this point the UI would display connection error, which is fine. Now, the real 702 // test is, can we recover? So I'll just repeat the above steps, without the failure. 703 // NOTE: everything from here down is copied from testOneUnread() and should be consolidated 704 705 // confirm that we're closed at this point 706 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 707 708 // and confirm that the next connection will be OK 709 checkOneUnread(mockTransport); 710 } 711 712 /** 713 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 714 * things should happen: We should see an intermediate failure that makes sense, and the next 715 * operation should reopen properly. 716 * 717 * There are multiple versions of this test because we have to check additional places where 718 * Pop3Store and/or Pop3Folder should be dealing with IOErrors. 719 * 720 * This test confirms that Pop3Store needs to call close() in the first IOExceptionHandler in 721 * Pop3Folder.open(). 722 */ 723 public void testCatchClosed5() { 724 // TODO cannot write this test until we can inject stream closures mid-sequence 725 } 726 727 /** 728 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 729 * things should happen: We should see an intermediate failure that makes sense, and the next 730 * operation should reopen properly. 731 * 732 * There are multiple versions of this test because we have to check additional places where 733 * Pop3Store and/or Pop3Folder should be dealing with IOErrors. 734 * 735 * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in 736 * Pop3Folder.open() (when it calls STAT and the response is empty of garbagey). 737 */ 738 public void testCatchClosed6a() throws MessagingException { 739 740 MockTransport mockTransport = openAndInjectMockTransport(); 741 742 // like openFolderWithMessage(mockTransport) but with a broken STAT report (empty response) 743 setupOpenFolder(mockTransport, -1, null); 744 try { 745 mFolder.open(OpenMode.READ_ONLY); 746 fail("Broken STAT should cause open() to throw."); 747 } catch(MessagingException me) { 748 // success 749 } 750 751 // At this point the UI would display connection error, which is fine. Now, the real 752 // test is, can we recover? So I'll try a new connection, without the failure. 753 754 // confirm that we're closed at this point 755 assertFalse("folder should be 'closed' after an IOError", mFolder.isOpen()); 756 757 // and confirm that the next connection will be OK 758 checkOneUnread(mockTransport); 759 } 760 761 /** 762 * Test the scenario where the transport is "open" but not really (e.g. server closed). Two 763 * things should happen: We should see an intermediate failure that makes sense, and the next 764 * operation should reopen properly. 765 * 766 * There are multiple versions of this test because we have to check additional places where 767 * Pop3Store and/or Pop3Folder should be dealing with IOErrors. 768 * 769 * This test confirms that Pop3Store needs to call close() in the second IOExceptionHandler in 770 * Pop3Folder.open() (when it calls STAT, and there is no response at all). 771 */ 772 public void testCatchClosed6b() { 773 // TODO cannot write this test until we can inject stream closures mid-sequence 774 } 775 776 /** 777 * Given an initialized mock transport, open it and attempt to "read" one unread message from 778 * it. This can be used as a basic test of functionality and it should be possible to call this 779 * repeatedly (if you close the folder between calls). 780 * 781 * @param mockTransport the mock transport we're using 782 */ 783 private void checkOneUnread(MockTransport mockTransport) throws MessagingException { 784 openFolderWithMessage(mockTransport); 785 786 // index the message(s) 787 setupUidlSequence(mockTransport, 1); 788 Message[] messages = mFolder.getMessages(1, 1, null); 789 assertEquals(1, messages.length); 790 assertEquals(getSingleMessageUID(1), messages[0].getUid()); 791 792 // try the basic fetch of flags & envelope 793 setupListSequence(mockTransport, 1); 794 FetchProfile fp = new FetchProfile(); 795 fp.add(FetchProfile.Item.FLAGS); 796 fp.add(FetchProfile.Item.ENVELOPE); 797 mFolder.fetch(messages, fp, null); 798 assertEquals(PER_MESSAGE_SIZE, messages[0].getSize()); 799 800 // A side effect of how messages work is that if you get fields that are empty, 801 // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom). The 802 // standard message parser needs to clear these before parsing. Make sure that this 803 // is happening. (This doesn't affect IMAP, which reads the headers directly via 804 // IMAP evelopes.) 805 MimeMessage message = (MimeMessage) messages[0]; 806 message.getRecipients(RecipientType.TO); 807 message.getRecipients(RecipientType.CC); 808 message.getRecipients(RecipientType.BCC); 809 810 // now try fetching the message 811 setupSingleMessage(mockTransport, 1, false); 812 fp = new FetchProfile(); 813 fp.add(FetchProfile.Item.BODY); 814 mFolder.fetch(messages, fp, null); 815 checkFetchedMessage(messages[0], 1, false); 816 } 817 818 /** 819 * A group of tests to confirm that we're properly juggling the RETR and TOP commands. 820 * Some servers (hello, live.com) support TOP but don't support CAPA. So we ignore CAPA 821 * and just try TOP. 822 */ 823 public void testRetrVariants() throws MessagingException { 824 MockTransport mockTransport = openAndInjectMockTransport(); 825 openFolderWithMessage(mockTransport); 826 827 // index the message(s) 828 setupUidlSequence(mockTransport, 2); 829 Message[] messages = mFolder.getMessages(1, 2, null); 830 assertEquals(2, messages.length); 831 832 // basic fetch of flags & envelope 833 setupListSequence(mockTransport, 2); 834 FetchProfile fp = new FetchProfile(); 835 fp.add(FetchProfile.Item.FLAGS); 836 fp.add(FetchProfile.Item.ENVELOPE); 837 mFolder.fetch(messages, fp, null); 838 839 // A side effect of how messages work is that if you get fields that are empty, 840 // then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom). The 841 // standard message parser needs to clear these before parsing. Make sure that this 842 // is happening. (This doesn't affect IMAP, which reads the headers directly via 843 // IMAP envelopes.) 844 for (Message message : messages) { 845 message.getRecipients(RecipientType.TO); 846 message.getRecipients(RecipientType.CC); 847 message.getRecipients(RecipientType.BCC); 848 } 849 850 // In the cases below, we fetch BODY_SANE which tries to load the first chunk of the 851 // message (not the entire thing) in order to quickly access the headers. 852 // In the first test, TOP succeeds 853 Message[] singleMessage = new Message[] { messages[0] }; 854 setupSingleMessageTop(mockTransport, 1, true, true); // try TOP & succeed 855 fp = new FetchProfile(); 856 fp.add(FetchProfile.Item.BODY_SANE); 857 mFolder.fetch(singleMessage, fp, null); 858 checkFetchedMessage(singleMessage[0], 1, false); 859 860 // In the 2nd test, TOP fails, so we should fall back to RETR 861 singleMessage[0] = messages[1]; 862 setupSingleMessageTop(mockTransport, 2, true, false); // try TOP & fail 863 fp = new FetchProfile(); 864 fp.add(FetchProfile.Item.BODY_SANE); 865 mFolder.fetch(singleMessage, fp, null); 866 checkFetchedMessage(singleMessage[0], 2, false); 867 } 868 869 /** 870 * Set up a basic MockTransport. open it, and inject it into mStore 871 */ 872 private MockTransport openAndInjectMockTransport() { 873 // Create mock transport and inject it into the POP3Store that's already set up 874 MockTransport mockTransport = new MockTransport(mContext, mHostAuth); 875 mockTransport.setSecurity(HostAuth.FLAG_NONE, false); 876 mStore.setTransport(mockTransport); 877 return mockTransport; 878 } 879 880 /** 881 * Open a folder that's preloaded with one unread message. 882 * 883 * @param mockTransport the mock transport we're using 884 */ 885 private void openFolderWithMessage(MockTransport mockTransport) throws MessagingException { 886 // try to open it 887 setupOpenFolder(mockTransport, 1, null); 888 mFolder.open(OpenMode.READ_ONLY); 889 890 // check message count 891 assertEquals(1, mFolder.getMessageCount()); 892 } 893 894 /** 895 * Look at a fetched message and confirm that it is complete. 896 * 897 * TODO this needs to be more dynamic, not just hardcoded for empty message #1. 898 * 899 * @param message the fetched message to be checked 900 * @param msgNum the message number 901 */ 902 private void checkFetchedMessage(Message message, int msgNum, boolean body) 903 throws MessagingException { 904 // check To: 905 Address[] to = message.getRecipients(RecipientType.TO); 906 assertNotNull(to); 907 assertEquals(1, to.length); 908 assertEquals("Smith@Registry.Org", to[0].getAddress()); 909 assertNull(to[0].getPersonal()); 910 911 // check From: 912 Address[] from = message.getFrom(); 913 assertNotNull(from); 914 assertEquals(1, from.length); 915 assertEquals("Jones@Registry.Org", from[0].getAddress()); 916 assertNull(from[0].getPersonal()); 917 918 // check Cc: 919 Address[] cc = message.getRecipients(RecipientType.CC); 920 assertNotNull(cc); 921 assertEquals(1, cc.length); 922 assertEquals("Chris@Registry.Org", cc[0].getAddress()); 923 assertNull(cc[0].getPersonal()); 924 925 // check Reply-To: 926 Address[] replyto = message.getReplyTo(); 927 assertNotNull(replyto); 928 assertEquals(1, replyto.length); 929 assertEquals("Roger@Registry.Org", replyto[0].getAddress()); 930 assertNull(replyto[0].getPersonal()); 931 932 // TODO date 933 934 // TODO check body (if applicable) 935 } 936 937 /** 938 * Helper which stuffs the mock with enough strings to satisfy a call to Pop3Folder.open() 939 * 940 * @param mockTransport the mock transport we're using 941 * @param statCount the number of messages to indicate in the STAT, or -1 for broken STAT 942 * @param capabilities if non-null, comma-separated list of capabilities 943 */ 944 private void setupOpenFolder(MockTransport mockTransport, int statCount, String capabilities) { 945 mockTransport.expect(null, "+OK Hello there from the Mock Transport."); 946 if (capabilities == null) { 947 mockTransport.expect("CAPA", "-ERR unimplemented"); 948 } else { 949 mockTransport.expect("CAPA", "+OK capabilities follow"); 950 mockTransport.expect(null, capabilities.split(",")); // one capability per line 951 mockTransport.expect(null, "."); // terminated by "." 952 } 953 mockTransport.expect("USER user", "+OK User name accepted"); 954 mockTransport.expect("PASS password", "+OK Logged in"); 955 if (statCount == -1) { 956 mockTransport.expect("STAT", ""); 957 } else { 958 String stat = "+OK " + Integer.toString(statCount) + " " 959 + Integer.toString(PER_MESSAGE_SIZE * statCount); 960 mockTransport.expect("STAT", stat); 961 } 962 } 963 964 /** 965 * Setup expects for a UIDL on a mailbox with 0 or more messages in it. 966 * @param transport The mock transport to preload 967 * @param numMessages The number of messages to return from UIDL. 968 */ 969 private static void setupUidlSequence(MockTransport transport, int numMessages) { 970 transport.expect("UIDL", "+OK sending UIDL list"); 971 for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { 972 transport.expect(null, Integer.toString(msgNum) + " " + getSingleMessageUID(msgNum)); 973 } 974 transport.expect(null, "."); 975 } 976 977 /** 978 * Setup expects for a LIST on a mailbox with 0 or more messages in it. 979 * @param transport The mock transport to preload 980 * @param numMessages The number of messages to return from LIST. 981 */ 982 private static void setupListSequence(MockTransport transport, int numMessages) { 983 transport.expect("LIST", "+OK sending scan listing"); 984 for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { 985 transport.expect(null, Integer.toString(msgNum) + " " + 986 Integer.toString(PER_MESSAGE_SIZE * msgNum)); 987 } 988 transport.expect(null, "."); 989 } 990 991 /** 992 * Setup expects for a LIST on a mailbox with 0 or more messages in it, except that 993 * this time the pipe fails, and we return empty lines. 994 * @param transport The mock transport to preload 995 * @param numMessages The number of messages to return from LIST. 996 */ 997 private static void setupBrokenListSequence(MockTransport transport, int numMessages) { 998 transport.expect("LIST", ""); 999 for (int msgNum = 1; msgNum <= numMessages; ++msgNum) { 1000 transport.expect(null, ""); 1001 } 1002 transport.expect(null, ""); 1003 } 1004 1005 /** 1006 * Setup a single message to be retrieved. 1007 * 1008 * Per RFC822 here is a minimal message header: 1009 * Date: 26 Aug 76 1429 EDT 1010 * From: Jones@Registry.Org 1011 * To: Smith@Registry.Org 1012 * 1013 * We'll add the following fields to support additional tests: 1014 * Cc: Chris@Registry.Org 1015 * Reply-To: Roger@Registry.Org 1016 * 1017 * @param transport the mock transport to preload 1018 * @param msgNum the message number to expect and return 1019 * @param body if true, a non-empty body will be added 1020 */ 1021 private static void setupSingleMessage(MockTransport transport, int msgNum, boolean body) { 1022 setupSingleMessageTop(transport, msgNum, false, false); 1023 } 1024 1025 /** 1026 * Setup a single message to be retrieved (headers only). 1027 * This is very similar to setupSingleMessage() but is intended to test the BODY_SANE 1028 * fetch mode. 1029 * @param transport the mock transport 1030 * @param msgNum the message number to expect and return 1031 * @param topTry if true, the "client" is going to attempt the TOP command 1032 * @param topSupported if true, the "server" supports the TOP command 1033 */ 1034 private static void setupSingleMessageTop(MockTransport transport, int msgNum, 1035 boolean topTry, boolean topSupported) { 1036 String msgNumString = Integer.toString(msgNum); 1037 String topCommand = "TOP " + msgNumString + " 673"; 1038 String retrCommand = "RETR " + msgNumString; 1039 1040 if (topTry) { 1041 if (topSupported) { 1042 transport.expect(topCommand, "+OK message follows"); 1043 } else { 1044 transport.expect(topCommand, "-ERR unsupported command"); 1045 transport.expect(retrCommand, "+OK message follows"); 1046 } 1047 } else { 1048 transport.expect(retrCommand, "+OK message follows"); 1049 } 1050 1051 transport.expect(null, "Date: 26 Aug 76 1429 EDT"); 1052 transport.expect(null, "From: Jones@Registry.Org"); 1053 transport.expect(null, "To: Smith@Registry.Org"); 1054 transport.expect(null, "CC: Chris@Registry.Org"); 1055 transport.expect(null, "Reply-To: Roger@Registry.Org"); 1056 transport.expect(null, ""); 1057 transport.expect(null, "."); 1058 } 1059 1060 /** 1061 * Generates a simple unique code for each message. Repeatable. 1062 * @param msgNum The message number 1063 * @return a string that can be used as the UID 1064 */ 1065 private static String getSingleMessageUID(int msgNum) { 1066 final String UID_HEAD = "ABCDEF-"; 1067 final String UID_TAIL = ""; 1068 return UID_HEAD + Integer.toString(msgNum) + UID_TAIL; 1069 } 1070} 1071