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