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