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.content.ContextWrapper; 21 import android.content.SharedPreferences; 22 import android.content.pm.PackageManager.NameNotFoundException; 23 import android.os.Build; 24 import android.os.Bundle; 25 import android.test.InstrumentationTestCase; 26 import android.test.MoreAsserts; 27 import android.test.suitebuilder.annotation.SmallTest; 28 import android.test.suitebuilder.annotation.Suppress; 29 30 import com.android.email.DBTestHelper; 31 import com.android.email.MockSharedPreferences; 32 import com.android.email.MockVendorPolicy; 33 import com.android.email.mail.store.ImapStore.ImapMessage; 34 import com.android.email.mail.store.imap.ImapResponse; 35 import com.android.email.mail.store.imap.ImapTestUtils; 36 import com.android.email.mail.transport.MockTransport; 37 import com.android.emailcommon.TempDirectory; 38 import com.android.emailcommon.VendorPolicyLoader; 39 import com.android.emailcommon.internet.MimeBodyPart; 40 import com.android.emailcommon.internet.MimeMultipart; 41 import com.android.emailcommon.internet.MimeUtility; 42 import com.android.emailcommon.internet.TextBody; 43 import com.android.emailcommon.mail.Address; 44 import com.android.emailcommon.mail.AuthenticationFailedException; 45 import com.android.emailcommon.mail.Body; 46 import com.android.emailcommon.mail.FetchProfile; 47 import com.android.emailcommon.mail.Flag; 48 import com.android.emailcommon.mail.Folder; 49 import com.android.emailcommon.mail.Folder.FolderType; 50 import com.android.emailcommon.mail.Folder.OpenMode; 51 import com.android.emailcommon.mail.Message; 52 import com.android.emailcommon.mail.Message.RecipientType; 53 import com.android.emailcommon.mail.MessagingException; 54 import com.android.emailcommon.mail.Part; 55 import com.android.emailcommon.provider.Account; 56 import com.android.emailcommon.provider.HostAuth; 57 import com.android.emailcommon.provider.Mailbox; 58 import com.android.emailcommon.utility.Utility; 59 60 import org.apache.commons.io.IOUtils; 61 62 import java.util.ArrayList; 63 import java.util.HashMap; 64 import java.util.regex.Pattern; 65 66 /** 67 * This is a series of unit tests for the ImapStore class. These tests must be locally 68 * complete - no server(s) required. 69 * 70 * To run these tests alone, use: 71 * $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email 72 * 73 * TODO Check if callback is really called 74 * TODO test for BAD response in various places? 75 * TODO test for BYE response in various places? 76 */ 77 @Suppress 78 @SmallTest 79 public class ImapStoreUnitTests extends InstrumentationTestCase { 80 private final static String[] NO_REPLY = new String[0]; 81 82 /** Default folder name. In order to test for encoding, we use a non-ascii name. */ 83 private final static String FOLDER_NAME = "\u65E5"; 84 /** Folder name encoded in UTF-7. */ 85 private final static String FOLDER_ENCODED = "&ZeU-"; 86 /** 87 * Flag bits to specify whether or not a folder can be selected. This corresponds to 88 * {@link Mailbox#FLAG_ACCEPTS_MOVED_MAIL} and {@link Mailbox#FLAG_HOLDS_MAIL}. 89 */ 90 private final static int SELECTABLE_BITS = 0x18; 91 92 private final static ImapResponse CAPABILITY_RESPONSE = ImapTestUtils.parseResponse( 93 "* CAPABILITY IMAP4rev1 STARTTLS"); 94 95 /* These values are provided by setUp() */ 96 private ImapStore mStore = null; 97 private ImapFolder mFolder = null; 98 private Context mTestContext; 99 private HostAuth mHostAuth; 100 101 /** The tag for the current IMAP command; used for mock transport responses */ 102 private int mTag; 103 // Fields specific to the CopyMessages tests 104 private MockTransport mCopyMock; 105 private Folder mCopyToFolder; 106 private Message[] mCopyMessages; 107 108 /** 109 * A wrapper to provide a wrapper to a Context which has already been mocked. 110 * This allows additional methods to delegate to the original, real context, in cases 111 * where the mocked behavior is insufficient. 112 */ 113 private class SecondaryMockContext extends ContextWrapper { 114 private final Context mUnderlying; 115 SecondaryMockContext(Context mocked, Context underlying)116 public SecondaryMockContext(Context mocked, Context underlying) { 117 super(mocked); 118 mUnderlying = underlying; 119 } 120 121 // TODO: eliminate the need for these method. 122 @Override createPackageContext(String packageName, int flags)123 public Context createPackageContext(String packageName, int flags) 124 throws NameNotFoundException { 125 return mUnderlying.createPackageContext(packageName, flags); 126 } 127 128 @Override getSharedPreferences(String name, int mode)129 public SharedPreferences getSharedPreferences(String name, int mode) { 130 return new MockSharedPreferences(); 131 } 132 } 133 134 /** 135 * Setup code. We generate a lightweight ImapStore and ImapStore.ImapFolder. 136 */ 137 @Override setUp()138 protected void setUp() throws Exception { 139 super.setUp(); 140 Context realContext = getInstrumentation().getTargetContext(); 141 ImapStore.sImapId = ImapStore.makeCommonImapId(realContext.getPackageName(), 142 Build.VERSION.RELEASE, Build.VERSION.CODENAME, 143 Build.MODEL, Build.ID, Build.MANUFACTURER, 144 "FakeNetworkOperator"); 145 mTestContext = new SecondaryMockContext( 146 DBTestHelper.ProviderContextSetupHelper.getProviderContext(realContext), 147 realContext); 148 MockVendorPolicy.inject(mTestContext); 149 150 TempDirectory.setTempDirectory(mTestContext); 151 152 // These are needed so we can get at the inner classes 153 HostAuth testAuth = new HostAuth(); 154 Account testAccount = new Account(); 155 156 testAuth.setLogin("user", "password"); 157 testAuth.setConnection("imap", "server", 999); 158 testAccount.mHostAuthRecv = testAuth; 159 mStore = (ImapStore) ImapStore.newInstance(testAccount, mTestContext); 160 mFolder = (ImapFolder) mStore.getFolder(FOLDER_NAME); 161 resetTag(); 162 } 163 testJoinMessageUids()164 public void testJoinMessageUids() throws Exception { 165 assertEquals("", ImapStore.joinMessageUids(new Message[] {})); 166 assertEquals("a", ImapStore.joinMessageUids(new Message[] { 167 mFolder.createMessage("a") 168 })); 169 assertEquals("a,XX", ImapStore.joinMessageUids(new Message[] { 170 mFolder.createMessage("a"), 171 mFolder.createMessage("XX"), 172 })); 173 } 174 175 /** 176 * Confirms simple non-SSL non-TLS login 177 */ testSimpleLogin()178 public void testSimpleLogin() throws MessagingException { 179 180 MockTransport mockTransport = openAndInjectMockTransport(); 181 182 // try to open it 183 setupOpenFolder(mockTransport); 184 mFolder.open(OpenMode.READ_WRITE); 185 186 // TODO: inject specific facts in the initial folder SELECT and check them here 187 } 188 189 /** 190 * Test simple login with failed authentication 191 */ testLoginFailure()192 public void testLoginFailure() throws Exception { 193 MockTransport mockTransport = openAndInjectMockTransport(); 194 expectLogin(mockTransport, false, false, false, new String[] {"* iD nIL", "oK"}, 195 "nO authentication failed"); 196 197 try { 198 mStore.getConnection().open(); 199 fail("Didn't throw AuthenticationFailedException"); 200 } catch (AuthenticationFailedException expected) { 201 } 202 } 203 204 /** 205 * Test simple TLS open 206 */ testTlsOpen()207 public void testTlsOpen() throws MessagingException { 208 209 MockTransport mockTransport = openAndInjectMockTransport(HostAuth.FLAG_TLS, 210 false); 211 212 // try to open it, with STARTTLS 213 expectLogin(mockTransport, true, false, false, 214 new String[] {"* iD nIL", "oK"}, "oK user authenticated (Success)"); 215 mockTransport.expect( 216 getNextTag(false) + " SELECT \"" + FOLDER_ENCODED + "\"", new String[] { 217 "* fLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)", 218 "* oK [pERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]", 219 "* 0 eXISTS", 220 "* 0 rECENT", 221 "* OK [uNSEEN 0]", 222 "* OK [uIDNEXT 1]", 223 getNextTag(true) + " oK [" + "rEAD-wRITE" + "] " + 224 FOLDER_ENCODED + " selected. (Success)"}); 225 226 mFolder.open(OpenMode.READ_WRITE); 227 assertTrue(mockTransport.isTlsStarted()); 228 } 229 230 /** 231 * TODO: Test with SSL negotiation (faked) 232 * TODO: Test with SSL required but not supported 233 * TODO: Test with TLS required but not supported 234 */ 235 236 /** 237 * Test the generation of the IMAP ID keys 238 */ testImapIdBasic()239 public void testImapIdBasic() { 240 // First test looks at operation of the outer API - we don't control any of the 241 // values; Just look for basic results. 242 243 // Strings we'll expect to find: 244 // name Android package name of the program 245 // os "android" 246 // os-version "version; build-id" 247 // vendor Vendor of the client/server 248 // x-android-device-model Model (Optional, so not tested here) 249 // x-android-net-operator Carrier (Unreliable, so not tested here) 250 // AGUID A device+account UID 251 String id = ImapStore.getImapId(mTestContext, "user-name", "host-name", 252 CAPABILITY_RESPONSE.flatten()); 253 HashMap<String, String> map = tokenizeImapId(id); 254 assertEquals(mTestContext.getPackageName(), map.get("name")); 255 assertEquals("android", map.get("os")); 256 assertNotNull(map.get("os-version")); 257 assertNotNull(map.get("vendor")); 258 assertNotNull(map.get("AGUID")); 259 260 // Next, use the inner API to confirm operation of a couple of 261 // variants for release and non-release devices. 262 263 // simple API check - non-REL codename, non-empty version 264 id = ImapStore.makeCommonImapId("packageName", "version", "codeName", 265 "model", "id", "vendor", "network-operator"); 266 map = tokenizeImapId(id); 267 assertEquals("packageName", map.get("name")); 268 assertEquals("android", map.get("os")); 269 assertEquals("version; id", map.get("os-version")); 270 assertEquals("vendor", map.get("vendor")); 271 assertEquals(null, map.get("x-android-device-model")); 272 assertEquals("network-operator", map.get("x-android-mobile-net-operator")); 273 assertEquals(null, map.get("AGUID")); 274 275 // simple API check - codename is REL, so use model name. 276 // also test empty version => 1.0 and empty network operator 277 id = ImapStore.makeCommonImapId("packageName", "", "REL", 278 "model", "id", "vendor", ""); 279 map = tokenizeImapId(id); 280 assertEquals("packageName", map.get("name")); 281 assertEquals("android", map.get("os")); 282 assertEquals("1.0; id", map.get("os-version")); 283 assertEquals("vendor", map.get("vendor")); 284 assertEquals("model", map.get("x-android-device-model")); 285 assertEquals(null, map.get("x-android-mobile-net-operator")); 286 assertEquals(null, map.get("AGUID")); 287 } 288 289 /** 290 * Test for the interaction between {@link ImapStore#getImapId} and a vendor policy. 291 */ testImapIdWithVendorPolicy()292 public void testImapIdWithVendorPolicy() { 293 try { 294 MockVendorPolicy.inject(mTestContext); 295 296 // Prepare mock result 297 Bundle result = new Bundle(); 298 result.putString("getImapId", "\"test-key\" \"test-value\""); 299 MockVendorPolicy.mockResult = result; 300 301 // Invoke 302 String id = ImapStore.getImapId(mTestContext, "user-name", "host-name", 303 ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z").flatten()); 304 305 // Check the result 306 assertEquals("test-value", tokenizeImapId(id).get("test-key")); 307 308 // Verify what's passed to the policy 309 assertEquals("getImapId", MockVendorPolicy.passedPolicy); 310 assertEquals("user-name", MockVendorPolicy.passedBundle.getString("getImapId.user")); 311 assertEquals("host-name", MockVendorPolicy.passedBundle.getString("getImapId.host")); 312 assertEquals("[CAPABILITY,IMAP4rev1,XXX,YYY,Z]", 313 MockVendorPolicy.passedBundle.getString("getImapId.capabilities")); 314 } finally { 315 VendorPolicyLoader.clearInstanceForTest(); 316 } 317 } 318 319 /** 320 * Test of the internal generator for IMAP ID strings, specifically looking for proper 321 * filtering of illegal values. This is required because we cannot necessarily trust 322 * the external sources of some of this data (e.g. release labels). 323 * 324 * The (somewhat arbitrary) legal values are: a-z A-Z 0-9 - _ + = ; : . , / <space> 325 * The most important goal of the filters is to keep out control chars, (, ), and " 326 */ testImapIdFiltering()327 public void testImapIdFiltering() { 328 String id = ImapStore.makeCommonImapId( 329 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 330 "0123456789", "codeName", 331 "model", "-_+=;:.,// ", 332 "v(e)n\"d\ro\nr", // look for bad chars stripped out, leaving OK chars 333 "()\""); // look for bad chars stripped out, leaving nothing 334 HashMap<String, String> map = tokenizeImapId(id); 335 336 assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name")); 337 assertEquals("0123456789; -_+=;:.,// ", map.get("os-version")); 338 assertEquals("vendor", map.get("vendor")); 339 assertNull(map.get("x-android-mobile-net-operator")); 340 } 341 342 /** 343 * Test that IMAP ID uid's are per-username 344 */ testImapIdDeviceId()345 public void testImapIdDeviceId() throws MessagingException { 346 HostAuth testAuth; 347 Account testAccount; 348 349 // store 1a 350 testAuth = new HostAuth(); 351 testAuth.setLogin("user1", "password"); 352 testAuth.setConnection("imap", "server", 999); 353 testAccount = new Account(); 354 testAccount.mHostAuthRecv = testAuth; 355 ImapStore testStore1A = (ImapStore) ImapStore.newInstance(testAccount, mTestContext); 356 357 // store 1b 358 testAuth = new HostAuth(); 359 testAuth.setLogin("user1", "password"); 360 testAuth.setConnection("imap", "server", 999); 361 testAccount = new Account(); 362 testAccount.mHostAuthRecv = testAuth; 363 ImapStore testStore1B = (ImapStore) ImapStore.newInstance(testAccount, mTestContext); 364 365 // store 2 366 testAuth = new HostAuth(); 367 testAuth.setLogin("user2", "password"); 368 testAuth.setConnection("imap", "server", 999); 369 testAccount = new Account(); 370 testAccount.mHostAuthRecv = testAuth; 371 ImapStore testStore2 = (ImapStore) ImapStore.newInstance(testAccount, mTestContext); 372 373 String capabilities = CAPABILITY_RESPONSE.flatten(); 374 String id1a = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities); 375 String id1b = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities); 376 String id2 = ImapStore.getImapId(mTestContext, "user2", "host-name", capabilities); 377 378 String uid1a = tokenizeImapId(id1a).get("AGUID"); 379 String uid1b = tokenizeImapId(id1b).get("AGUID"); 380 String uid2 = tokenizeImapId(id2).get("AGUID"); 381 382 assertEquals(uid1a, uid1b); 383 MoreAsserts.assertNotEqual(uid1a, uid2); 384 } 385 386 /** 387 * Helper to break an IMAP ID string into keys & values 388 * @param id the IMAP Id string (the part inside the parens) 389 * @return a map of key/value pairs 390 */ tokenizeImapId(String id)391 private HashMap<String, String> tokenizeImapId(String id) { 392 // Instead of a true tokenizer, we'll use double-quote as the split. 393 // We can's use " " because there may be spaces inside the values. 394 String[] elements = id.split("\""); 395 HashMap<String, String> map = new HashMap<String, String>(); 396 for (int i = 0; i < elements.length; ) { 397 // Because we split at quotes, we expect to find: 398 // [i] = null or one or more spaces 399 // [i+1] = key 400 // [i+2] = one or more spaces 401 // [i+3] = value 402 map.put(elements[i+1], elements[i+3]); 403 i += 4; 404 } 405 return map; 406 } 407 408 /** 409 * Test non-NIL server response to IMAP ID. We should simply ignore it. 410 */ testServerId()411 public void testServerId() throws MessagingException { 412 MockTransport mockTransport = openAndInjectMockTransport(); 413 414 // try to open it 415 setupOpenFolder(mockTransport, new String[] { 416 "* ID (\"name\" \"Cyrus\" \"version\" \"1.5\"" + 417 " \"os\" \"sunos\" \"os-version\" \"5.5\"" + 418 " \"support-url\" \"mailto:cyrus-bugs+@andrew.cmu.edu\")", 419 "oK"}, "rEAD-wRITE"); 420 mFolder.open(OpenMode.READ_WRITE); 421 } 422 423 /** 424 * Test OK response to IMAP ID with crummy text afterwards too. 425 */ testImapIdOkParsing()426 public void testImapIdOkParsing() throws MessagingException { 427 MockTransport mockTransport = openAndInjectMockTransport(); 428 429 // try to open it 430 setupOpenFolder(mockTransport, new String[] { 431 "* iD nIL", 432 "oK [iD] bad-char-%"}, "rEAD-wRITE"); 433 mFolder.open(OpenMode.READ_WRITE); 434 } 435 436 /** 437 * Test BAD response to IMAP ID - also with bad parser chars 438 */ testImapIdBad()439 public void testImapIdBad() throws MessagingException { 440 MockTransport mockTransport = openAndInjectMockTransport(); 441 442 // try to open it 443 setupOpenFolder(mockTransport, new String[] { 444 "bAD unknown command bad-char-%"}, "rEAD-wRITE"); 445 mFolder.open(OpenMode.READ_WRITE); 446 } 447 448 /** 449 * Confirm that when IMAP ID is not in capability, it is not sent/received. 450 * This supports RFC 2971 section 3, and is important because certain servers 451 * (e.g. imap.vodafone.net.nz) do not process the unexpected ID command properly. 452 */ testImapIdNotSupported()453 public void testImapIdNotSupported() throws MessagingException { 454 MockTransport mockTransport = openAndInjectMockTransport(); 455 456 // try to open it 457 setupOpenFolder(mockTransport, null, "rEAD-wRITE"); 458 mFolder.open(OpenMode.READ_WRITE); 459 } 460 461 /** 462 * Confirm that the non-conformant IMAP ID result seen on imap.secureserver.net fails 463 * to properly parse. 464 * 2 ID ("name" "com.google.android.email") 465 * * ID( "name" "Godaddy IMAP" ... "version" "3.1.0") 466 * 2 OK ID completed 467 */ testImapIdSecureServerParseFail()468 public void testImapIdSecureServerParseFail() { 469 MockTransport mockTransport = openAndInjectMockTransport(); 470 471 // configure mock server to return malformed ID response 472 setupOpenFolder(mockTransport, new String[] { 473 "* ID( \"name\" \"Godaddy IMAP\" \"version\" \"3.1.0\")", 474 "oK"}, "rEAD-wRITE"); 475 try { 476 mFolder.open(OpenMode.READ_WRITE); 477 fail("Expected MessagingException"); 478 } catch (MessagingException expected) { 479 } 480 } 481 482 /** 483 * Confirm that the connections to *.secureserver.net never send IMAP ID (see 484 * testImapIdSecureServerParseFail() for the reason why.) 485 */ testImapIdSecureServerNotSent()486 public void testImapIdSecureServerNotSent() throws MessagingException { 487 // Note, this is injected into mStore (which we don't use for this test) 488 MockTransport mockTransport = openAndInjectMockTransport(); 489 mockTransport.setHost("eMail.sEcurEserVer.nEt"); 490 491 // Prime the expects pump as if the server wants IMAP ID, but we should not actually expect 492 // to send it, because the login code in the store should never actually send it (to this 493 // particular server). This sequence is a minimized version of expectLogin(). 494 495 // Respond to the initial connection 496 mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You"); 497 // Return "ID" in the capability 498 expectCapability(mockTransport, true, false); 499 // No TLS 500 // No ID (the special case for this server) 501 // LOGIN 502 mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"", 503 getNextTag(true) + " " + "oK user authenticated (Success)"); 504 // SELECT 505 expectSelect(mockTransport, FOLDER_ENCODED, "rEAD-wRITE"); 506 507 // Now open the folder. Although the server indicates ID in the capabilities, 508 // we are not expecting the store to send the ID command (to this particular server). 509 mFolder.open(OpenMode.READ_WRITE); 510 } 511 512 /** 513 * Test small Folder functions that don't really do anything in Imap 514 */ testSmallFolderFunctions()515 public void testSmallFolderFunctions() { 516 // canCreate() returns true 517 assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS)); 518 assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES)); 519 } 520 521 /** 522 * Lightweight test to confirm that IMAP hasn't implemented any folder roles yet. 523 * 524 * TODO: Test this with multiple folders provided by mock server 525 * TODO: Implement XLIST and then support this 526 */ testNoFolderRolesYet()527 public void testNoFolderRolesYet() { 528 assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole()); 529 } 530 531 /** 532 * Lightweight test to confirm that IMAP is requesting sent-message-upload. 533 * TODO: Implement Gmail-specific cases and handle this server-side 534 */ testSentUploadRequested()535 public void testSentUploadRequested() { 536 assertTrue(mStore.requireCopyMessageToSentFolder()); 537 } 538 539 /** 540 * TODO: Test the process of opening and indexing a mailbox with one unread message in it. 541 */ 542 543 /** 544 * TODO: Test the scenario where the transport is "open" but not really (e.g. server closed). 545 /** 546 * Set up a basic MockTransport. open it, and inject it into mStore 547 */ openAndInjectMockTransport()548 private MockTransport openAndInjectMockTransport() { 549 return openAndInjectMockTransport(HostAuth.FLAG_NONE, false); 550 } 551 552 /** 553 * Set up a MockTransport with security settings 554 */ openAndInjectMockTransport(int connectionSecurity, boolean trustAllCertificates)555 private MockTransport openAndInjectMockTransport(int connectionSecurity, 556 boolean trustAllCertificates) { 557 // Create mock transport and inject it into the ImapStore that's already set up 558 MockTransport mockTransport = MockTransport.createMockTransport(mTestContext); 559 mockTransport.setSecurity(connectionSecurity, trustAllCertificates); 560 mockTransport.setHost("mock.server.com"); 561 mStore.setTransportForTest(mockTransport); 562 return mockTransport; 563 } 564 565 /** 566 * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open() 567 * 568 * @param mockTransport the mock transport we're using 569 */ setupOpenFolder(MockTransport mockTransport)570 private void setupOpenFolder(MockTransport mockTransport) { 571 setupOpenFolder(mockTransport, "rEAD-wRITE"); 572 } 573 574 /** 575 * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open() 576 * 577 * @param mockTransport the mock transport we're using 578 */ setupOpenFolder(MockTransport mockTransport, String readWriteMode)579 private void setupOpenFolder(MockTransport mockTransport, String readWriteMode) { 580 setupOpenFolder(mockTransport, new String[] { 581 "* iD nIL", "oK"}, readWriteMode, false); 582 } 583 584 /** 585 * Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open() 586 * Also allows setting a custom IMAP ID. 587 * 588 * Also sets mNextTag, an int, which is useful if there are additional commands to inject. 589 * 590 * @param mockTransport the mock transport we're using 591 * @param imapIdResponse the expected series of responses to the IMAP ID command. Non-final 592 * lines should be tagged with *. The final response should be untagged (the correct 593 * tag will be added at runtime). Pass "null" to test w/o IMAP ID. 594 * @param readWriteMode "READ-WRITE" or "READ-ONLY" 595 */ setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse, String readWriteMode)596 private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse, 597 String readWriteMode) { 598 setupOpenFolder(mockTransport, imapIdResponse, readWriteMode, false); 599 } 600 setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse, String readWriteMode, boolean withUidPlus)601 private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse, 602 String readWriteMode, boolean withUidPlus) { 603 expectLogin(mockTransport, imapIdResponse, withUidPlus); 604 expectSelect(mockTransport, FOLDER_ENCODED, readWriteMode); 605 } 606 607 /** 608 * Helper which stuffs the mock with the strings to satisfy a typical SELECT. 609 * @param mockTransport the mock transport we're using 610 * @param readWriteMode "READ-WRITE" or "READ-ONLY" 611 */ expectSelect(MockTransport mockTransport, String folder, String readWriteMode)612 private void expectSelect(MockTransport mockTransport, String folder, String readWriteMode) { 613 mockTransport.expect( 614 getNextTag(false) + " SELECT \"" + folder + "\"", new String[] { 615 "* fLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)", 616 "* oK [pERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]", 617 "* 0 eXISTS", 618 "* 0 rECENT", 619 "* OK [uNSEEN 0]", 620 "* OK [uIDNEXT 1]", 621 getNextTag(true) + " oK [" + readWriteMode + "] " + 622 folder + " selected. (Success)"}); 623 } 624 expectLogin(MockTransport mockTransport)625 private void expectLogin(MockTransport mockTransport) { 626 expectLogin(mockTransport, new String[] {"* iD nIL", "oK"}, false); 627 } 628 expectLogin(MockTransport mockTransport, String[] imapIdResponse, boolean withUidPlus)629 private void expectLogin(MockTransport mockTransport, String[] imapIdResponse, 630 boolean withUidPlus) { 631 expectLogin(mockTransport, false, (imapIdResponse != null), withUidPlus, imapIdResponse, 632 "oK user authenticated (Success)"); 633 } 634 expectLogin(MockTransport mockTransport, boolean startTls, boolean withId, boolean withUidPlus, String[] imapIdResponse, String loginResponse)635 private void expectLogin(MockTransport mockTransport, boolean startTls, boolean withId, 636 boolean withUidPlus, String[] imapIdResponse, String loginResponse) { 637 // inject boilerplate commands that match our typical login 638 mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You"); 639 640 expectCapability(mockTransport, withId, withUidPlus); 641 642 // TLS (if expected) 643 if (startTls) { 644 mockTransport.expect(getNextTag(false) + " STARTTLS", 645 getNextTag(true) + " Ok starting TLS"); 646 mockTransport.expectStartTls(); 647 // After switching to TLS the client must re-query for capability 648 expectCapability(mockTransport, withId, withUidPlus); 649 } 650 651 // ID 652 if (withId) { 653 String expectedNextTag = getNextTag(false); 654 // Fix the tag # of the ID response 655 String last = imapIdResponse[imapIdResponse.length-1]; 656 last = expectedNextTag + " " + last; 657 imapIdResponse[imapIdResponse.length-1] = last; 658 mockTransport.expect(getNextTag(false) + " ID \\(.*\\)", imapIdResponse); 659 getNextTag(true); // Advance the tag for ID response. 660 } 661 662 // LOGIN 663 mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"", 664 getNextTag(true) + " " + loginResponse); 665 } 666 expectCapability(MockTransport mockTransport, boolean withId, boolean withUidPlus)667 private void expectCapability(MockTransport mockTransport, boolean withId, 668 boolean withUidPlus) { 669 String capabilityList = "* cAPABILITY iMAP4rev1 sTARTTLS aUTH=gSSAPI lOGINDISABLED"; 670 capabilityList += withId ? " iD" : ""; 671 capabilityList += withUidPlus ? " UiDPlUs" : ""; 672 673 mockTransport.expect(getNextTag(false) + " CAPABILITY", new String[] { 674 capabilityList, 675 getNextTag(true) + " oK CAPABILITY completed"}); 676 } 677 expectNoop(MockTransport mockTransport, boolean ok)678 private void expectNoop(MockTransport mockTransport, boolean ok) { 679 String response = ok ? " oK success" : " nO timeout"; 680 mockTransport.expect(getNextTag(false) + " NOOP", 681 new String[] {getNextTag(true) + response}); 682 } 683 684 /** 685 * Return a tag for use in setting up expect strings. Typically this is called in pairs, 686 * first as getNextTag(false) when emitting the command, then as getNextTag(true) when 687 * emitting the final line of the expected response. 688 * @param advance true to increment mNextTag for the subsequence command 689 * @return a string containing the current tag 690 */ getNextTag(boolean advance)691 public String getNextTag(boolean advance) { 692 if (advance) ++mTag; 693 return Integer.toString(mTag); 694 } 695 696 /** 697 * Resets the tag back to it's starting value. Do this after the test connection has been 698 * closed. 699 */ resetTag()700 private int resetTag() { 701 return resetTag(1); 702 } 703 resetTag(int tag)704 private int resetTag(int tag) { 705 int oldTag = mTag; 706 mTag = tag; 707 return oldTag; 708 } 709 710 /** 711 * Test that servers reporting READ-WRITE mode are parsed properly 712 * Note: the READ_WRITE mode passed to folder.open() does not affect the test 713 */ testReadWrite()714 public void testReadWrite() throws MessagingException { 715 MockTransport mock = openAndInjectMockTransport(); 716 setupOpenFolder(mock, "rEAD-WRITE"); 717 mFolder.open(OpenMode.READ_WRITE); 718 assertEquals(OpenMode.READ_WRITE, mFolder.getMode()); 719 } 720 721 /** 722 * Test that servers reporting READ-ONLY mode are parsed properly 723 * Note: the READ_ONLY mode passed to folder.open() does not affect the test 724 */ testReadOnly()725 public void testReadOnly() throws MessagingException { 726 MockTransport mock = openAndInjectMockTransport(); 727 setupOpenFolder(mock, "rEAD-ONLY"); 728 mFolder.open(OpenMode.READ_ONLY); 729 assertEquals(OpenMode.READ_ONLY, mFolder.getMode()); 730 } 731 732 /** 733 * Test for getUnreadMessageCount with quoted string in the middle of response. 734 */ testGetUnreadMessageCountWithQuotedString()735 public void testGetUnreadMessageCountWithQuotedString() throws Exception { 736 MockTransport mock = openAndInjectMockTransport(); 737 setupOpenFolder(mock); 738 mock.expect( 739 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)", 740 new String[] { 741 "* sTATUS \"" + FOLDER_ENCODED + "\" (uNSEEN 2)", 742 getNextTag(true) + " oK STATUS completed"}); 743 mFolder.open(OpenMode.READ_WRITE); 744 int unreadCount = mFolder.getUnreadMessageCount(); 745 assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount); 746 } 747 748 /** 749 * Test for getUnreadMessageCount with literal string in the middle of response. 750 */ testGetUnreadMessageCountWithLiteralString()751 public void testGetUnreadMessageCountWithLiteralString() throws Exception { 752 MockTransport mock = openAndInjectMockTransport(); 753 setupOpenFolder(mock); 754 mock.expect( 755 getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)", 756 new String[] { 757 "* sTATUS {5}", 758 FOLDER_ENCODED + " (uNSEEN 10)", 759 getNextTag(true) + " oK STATUS completed"}); 760 mFolder.open(OpenMode.READ_WRITE); 761 int unreadCount = mFolder.getUnreadMessageCount(); 762 assertEquals("getUnreadMessageCount with literal string", 10, unreadCount); 763 } 764 testFetchFlagEnvelope()765 public void testFetchFlagEnvelope() throws MessagingException { 766 final MockTransport mock = openAndInjectMockTransport(); 767 setupOpenFolder(mock); 768 mFolder.open(OpenMode.READ_WRITE); 769 final Message message = mFolder.createMessage("1"); 770 771 final FetchProfile fp = new FetchProfile(); 772 fp.add(FetchProfile.Item.FLAGS); 773 fp.add(FetchProfile.Item.ENVELOPE); 774 mock.expect(getNextTag(false) + 775 " UID FETCH 1 \\(UID FLAGS INTERNALDATE RFC822\\.SIZE BODY\\.PEEK\\[HEADER.FIELDS" + 776 " \\(date subject from content-type to cc message-id\\)\\]\\)", 777 new String[] { 778 "* 9 fETCH (uID 1 rFC822.sIZE 120626 iNTERNALDATE \"17-may-2010 22:00:15 +0000\"" + 779 "fLAGS (\\Seen) bODY[hEADER.FIELDS (dAte sUbject fRom cOntent-type tO cC" + 780 " mEssage-id)]" + 781 " {279}", 782 "From: Xxxxxx Yyyyy <userxx@android.com>", 783 "Date: Mon, 17 May 2010 14:59:52 -0700", 784 "Message-ID: <x0000000000000000000000000000000000000000000000y@android.com>", 785 "Subject: ssubject", 786 "To: android.test01@android.com", 787 "Content-Type: multipart/mixed; boundary=a00000000000000000000000000b", 788 "", 789 ")", 790 getNextTag(true) + " oK SUCCESS" 791 }); 792 mFolder.fetch(new Message[] { message }, fp, null); 793 794 assertEquals("android.test01@android.com", message.getHeader("to")[0]); 795 assertEquals("Xxxxxx Yyyyy <userxx@android.com>", message.getHeader("from")[0]); 796 assertEquals("multipart/mixed; boundary=a00000000000000000000000000b", 797 message.getHeader("Content-Type")[0]); 798 assertTrue(message.isSet(Flag.SEEN)); 799 800 // TODO: Test NO response. 801 } 802 803 /** 804 * Test for fetching simple BODYSTRUCTURE. 805 */ testFetchBodyStructureSimple()806 public void testFetchBodyStructureSimple() throws Exception { 807 final MockTransport mock = openAndInjectMockTransport(); 808 setupOpenFolder(mock); 809 mFolder.open(OpenMode.READ_WRITE); 810 final Message message = mFolder.createMessage("1"); 811 812 final FetchProfile fp = new FetchProfile(); 813 fp.add(FetchProfile.Item.STRUCTURE); 814 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", 815 new String[] { 816 "* 9 fETCH (uID 1 bODYSTRUCTURE (\"tEXT\" \"pLAIN\" nIL" + 817 " nIL nIL nIL 18 3 nIL nIL nIL))", 818 getNextTag(true) + " oK sUCCESS" 819 }); 820 mFolder.fetch(new Message[] { message }, fp, null); 821 822 // Check mime structure... 823 MoreAsserts.assertEquals( 824 new String[] {"text/plain"}, 825 message.getHeader("Content-Type") 826 ); 827 assertNull(message.getHeader("Content-Transfer-Encoding")); 828 assertNull(message.getHeader("Content-ID")); 829 MoreAsserts.assertEquals( 830 new String[] {";\n size=18"}, 831 message.getHeader("Content-Disposition") 832 ); 833 834 MoreAsserts.assertEquals( 835 new String[] {"TEXT"}, 836 message.getHeader("X-Android-Attachment-StoreData") 837 ); 838 839 // TODO: Test NO response. 840 } 841 842 /** 843 * Test for fetching complex muiltipart BODYSTRUCTURE. 844 */ testFetchBodyStructureMultipart()845 public void testFetchBodyStructureMultipart() throws Exception { 846 final MockTransport mock = openAndInjectMockTransport(); 847 setupOpenFolder(mock); 848 mFolder.open(OpenMode.READ_WRITE); 849 final Message message = mFolder.createMessage("1"); 850 851 final FetchProfile fp = new FetchProfile(); 852 fp.add(FetchProfile.Item.STRUCTURE); 853 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", 854 new String[] { 855 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"pLAIN\" () {20}", 856 "long content id#@!@#" + 857 " NIL \"7BIT\" 18 3 NIL NIL NIL)" + 858 "(\"IMAGE\" \"PNG\" (\"NAME\" {10}", 859 "device.png) NIL NIL \"BASE64\" {6}", 860 "117840 NIL (\"aTTACHMENT\" (\"fILENAME\" \"device.png\")) NIL)" + 861 "(\"TEXT\" \"HTML\" () NIL NIL \"7BIT\" 100 NIL 123 (\"aTTACHMENT\"" + 862 "(\"fILENAME\" {15}", 863 "attachment.html \"SIZE\" 555)) NIL)" + 864 "((\"TEXT\" \"HTML\" NIL NIL \"BASE64\")(\"XXX\" \"YYY\"))" + // Nested 865 "\"mIXED\" (\"bOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))", 866 getNextTag(true) + " oK SUCCESS" 867 }); 868 mFolder.fetch(new Message[] { message }, fp, null); 869 870 // Check mime structure... 871 final Body body = message.getBody(); 872 assertTrue(body instanceof MimeMultipart); 873 MimeMultipart mimeMultipart = (MimeMultipart) body; 874 assertEquals(4, mimeMultipart.getCount()); 875 assertEquals("mixed", mimeMultipart.getSubTypeForTest()); 876 877 final Part part1 = mimeMultipart.getBodyPart(0); 878 final Part part2 = mimeMultipart.getBodyPart(1); 879 final Part part3 = mimeMultipart.getBodyPart(2); 880 final Part part4 = mimeMultipart.getBodyPart(3); 881 assertTrue(part1 instanceof MimeBodyPart); 882 assertTrue(part2 instanceof MimeBodyPart); 883 assertTrue(part3 instanceof MimeBodyPart); 884 assertTrue(part4 instanceof MimeBodyPart); 885 886 final MimeBodyPart mimePart1 = (MimeBodyPart) part1; // text/plain 887 final MimeBodyPart mimePart2 = (MimeBodyPart) part2; // image/png 888 final MimeBodyPart mimePart3 = (MimeBodyPart) part3; // text/html 889 final MimeBodyPart mimePart4 = (MimeBodyPart) part4; // Nested 890 891 MoreAsserts.assertEquals( 892 new String[] {"1"}, 893 part1.getHeader("X-Android-Attachment-StoreData") 894 ); 895 MoreAsserts.assertEquals( 896 new String[] {"2"}, 897 part2.getHeader("X-Android-Attachment-StoreData") 898 ); 899 MoreAsserts.assertEquals( 900 new String[] {"3"}, 901 part3.getHeader("X-Android-Attachment-StoreData") 902 ); 903 904 MoreAsserts.assertEquals( 905 new String[] {"text/plain"}, 906 part1.getHeader("Content-Type") 907 ); 908 MoreAsserts.assertEquals( 909 new String[] {"image/png;\n NAME=\"device.png\""}, 910 part2.getHeader("Content-Type") 911 ); 912 MoreAsserts.assertEquals( 913 new String[] {"text/html"}, 914 part3.getHeader("Content-Type") 915 ); 916 917 MoreAsserts.assertEquals( 918 new String[] {"long content id#@!@#"}, 919 part1.getHeader("Content-ID") 920 ); 921 assertNull(part2.getHeader("Content-ID")); 922 assertNull(part3.getHeader("Content-ID")); 923 924 MoreAsserts.assertEquals( 925 new String[] {"7BIT"}, 926 part1.getHeader("Content-Transfer-Encoding") 927 ); 928 MoreAsserts.assertEquals( 929 new String[] {"BASE64"}, 930 part2.getHeader("Content-Transfer-Encoding") 931 ); 932 MoreAsserts.assertEquals( 933 new String[] {"7BIT"}, 934 part3.getHeader("Content-Transfer-Encoding") 935 ); 936 937 MoreAsserts.assertEquals( 938 new String[] {";\n size=18"}, 939 part1.getHeader("Content-Disposition") 940 ); 941 MoreAsserts.assertEquals( 942 new String[] {"attachment;\n filename=\"device.png\";\n size=117840"}, 943 part2.getHeader("Content-Disposition") 944 ); 945 MoreAsserts.assertEquals( 946 new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""}, 947 part3.getHeader("Content-Disposition") 948 ); 949 950 // Check the nested parts. 951 final Body part4body = part4.getBody(); 952 assertTrue(part4body instanceof MimeMultipart); 953 MimeMultipart mimeMultipartPart4 = (MimeMultipart) part4body; 954 assertEquals(2, mimeMultipartPart4.getCount()); 955 956 final MimeBodyPart mimePart41 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(0); 957 final MimeBodyPart mimePart42 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(1); 958 959 MoreAsserts.assertEquals(new String[] {"4.1"}, 960 mimePart41.getHeader("X-Android-Attachment-StoreData") 961 ); 962 MoreAsserts.assertEquals(new String[] {"4.2"}, 963 mimePart42.getHeader("X-Android-Attachment-StoreData") 964 ); 965 } 966 testFetchBodySane()967 public void testFetchBodySane() throws MessagingException { 968 final MockTransport mock = openAndInjectMockTransport(); 969 setupOpenFolder(mock); 970 mFolder.open(OpenMode.READ_WRITE); 971 final Message message = mFolder.createMessage("1"); 972 973 final FetchProfile fp = new FetchProfile(); 974 fp.add(FetchProfile.Item.BODY_SANE); 975 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]<0.51200>\\)", 976 new String[] { 977 "* 9 fETCH (uID 1 bODY[] {23}", 978 "from: a@b.com", // 15 bytes 979 "", // 2 980 "test", // 6 981 ")", 982 getNextTag(true) + " oK SUCCESS" 983 }); 984 mFolder.fetch(new Message[] { message }, fp, null); 985 assertEquals("a@b.com", message.getHeader("from")[0]); 986 987 // TODO: Test NO response. 988 } 989 testFetchBody()990 public void testFetchBody() throws MessagingException { 991 final MockTransport mock = openAndInjectMockTransport(); 992 setupOpenFolder(mock); 993 mFolder.open(OpenMode.READ_WRITE); 994 final Message message = mFolder.createMessage("1"); 995 996 final FetchProfile fp = new FetchProfile(); 997 fp.add(FetchProfile.Item.BODY); 998 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]\\)", 999 new String[] { 1000 "* 9 fETCH (uID 1 bODY[] {23}", 1001 "from: a@b.com", // 15 bytes 1002 "", // 2 1003 "test", // 6 1004 ")", 1005 getNextTag(true) + " oK SUCCESS" 1006 }); 1007 mFolder.fetch(new Message[] { message }, fp, null); 1008 assertEquals("a@b.com", message.getHeader("from")[0]); 1009 1010 // TODO: Test NO response. 1011 } 1012 testFetchAttachment()1013 public void testFetchAttachment() throws Exception { 1014 MockTransport mock = openAndInjectMockTransport(); 1015 setupOpenFolder(mock); 1016 mFolder.open(OpenMode.READ_WRITE); 1017 final Message message = mFolder.createMessage("1"); 1018 1019 final FetchProfile fp = new FetchProfile(); 1020 fp.add(FetchProfile.Item.STRUCTURE); 1021 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", 1022 new String[] { 1023 "* 9 fETCH (uID 1 bODYSTRUCTURE ((\"tEXT\" \"PLAIN\" (\"cHARSET\" \"iSO-8859-1\")" + 1024 " CID nIL \"7bIT\" 18 3 NIL NIL NIL)" + 1025 "(\"IMAGE\" \"PNG\"" + 1026 " (\"nAME\" \"device.png\") NIL NIL \"bASE64\" 117840 NIL (\"aTTACHMENT\"" + 1027 "(\"fILENAME\" \"device.png\")) NIL)" + 1028 "\"mIXED\"))", 1029 getNextTag(true) + " OK SUCCESS" 1030 }); 1031 mFolder.fetch(new Message[] { message }, fp, null); 1032 1033 // Check mime structure, and get the second part. 1034 Body body = message.getBody(); 1035 assertTrue(body instanceof MimeMultipart); 1036 MimeMultipart mimeMultipart = (MimeMultipart) body; 1037 assertEquals(2, mimeMultipart.getCount()); 1038 1039 Part part1 = mimeMultipart.getBodyPart(1); 1040 assertTrue(part1 instanceof MimeBodyPart); 1041 MimeBodyPart mimePart1 = (MimeBodyPart) part1; 1042 1043 // Fetch the second part 1044 fp.clear(); 1045 fp.add(mimePart1); 1046 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[2\\]\\)", 1047 new String[] { 1048 "* 9 fETCH (uID 1 bODY[2] {4}", 1049 "YWJj)", // abc in base64 1050 getNextTag(true) + " oK SUCCESS" 1051 }); 1052 mFolder.fetch(new Message[] { message }, fp, null); 1053 1054 assertEquals("abc", 1055 Utility.fromUtf8(IOUtils.toByteArray(mimePart1.getBody().getInputStream()))); 1056 1057 // TODO: Test NO response. 1058 } 1059 1060 /** 1061 * Test for proper operations on servers that return "NIL" for empty message bodies. 1062 */ testNilMessage()1063 public void testNilMessage() throws MessagingException { 1064 MockTransport mock = openAndInjectMockTransport(); 1065 setupOpenFolder(mock); 1066 mFolder.open(OpenMode.READ_WRITE); 1067 1068 // Prepare to pull structure and peek body text - this is like the "large message" 1069 // loop in MessagingController.synchronizeMailboxGeneric() 1070 FetchProfile fp = new FetchProfile();fp.clear(); 1071 fp.add(FetchProfile.Item.STRUCTURE); 1072 Message message1 = mFolder.createMessage("1"); 1073 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] { 1074 "* 1 fETCH (uID 1 bODYSTRUCTURE (tEXT pLAIN nIL nIL nIL 7bIT 0 0 nIL nIL nIL))", 1075 getNextTag(true) + " oK SUCCESS" 1076 }); 1077 mFolder.fetch(new Message[] { message1 }, fp, null); 1078 1079 // The expected result for an empty body is: 1080 // * 1 FETCH (UID 1 BODY[TEXT] {0}) 1081 // But some servers are returning NIL for the empty body: 1082 // * 1 FETCH (UID 1 BODY[TEXT] NIL) 1083 // Because this breaks our little parser, fetch() skips over empty parts. 1084 // The rest of this test is confirming that this is the case. 1085 1086 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] { 1087 "* 1 fETCH (uID 1 bODY[tEXT] nIL)", 1088 getNextTag(true) + " oK SUCCESS" 1089 }); 1090 ArrayList<Part> viewables = new ArrayList<Part>(); 1091 ArrayList<Part> attachments = new ArrayList<Part>(); 1092 MimeUtility.collectParts(message1, viewables, attachments); 1093 assertTrue(viewables.size() == 1); 1094 Part emptyBodyPart = viewables.get(0); 1095 fp.clear(); 1096 fp.add(emptyBodyPart); 1097 mFolder.fetch(new Message[] { message1 }, fp, null); 1098 1099 // If this wasn't working properly, there would be an attempted interpretation 1100 // of the empty part's NIL and possibly a crash. 1101 1102 // If this worked properly, the "empty" body can now be retrieved 1103 viewables = new ArrayList<Part>(); 1104 attachments = new ArrayList<Part>(); 1105 MimeUtility.collectParts(message1, viewables, attachments); 1106 assertTrue(viewables.size() == 1); 1107 emptyBodyPart = viewables.get(0); 1108 String text = MimeUtility.getTextFromPart(emptyBodyPart); 1109 assertNull(text); 1110 } 1111 1112 /** 1113 * Confirm the IMAP parser won't crash when seeing an excess FETCH response line without UID. 1114 * 1115 * <p>We've observed that the secure.emailsrvr.com email server returns an excess FETCH response 1116 * for a UID FETCH command. These excess responses doesn't have the UID field in it, even 1117 * though we request, which led the response parser to crash. We fixed it by ignoring response 1118 * lines that don't have UID. This test is to make sure this case. 1119 */ testExcessFetchResult()1120 public void testExcessFetchResult() throws MessagingException { 1121 MockTransport mock = openAndInjectMockTransport(); 1122 setupOpenFolder(mock); 1123 mFolder.open(OpenMode.READ_WRITE); 1124 1125 // Create a message, and make sure it's not "SEEN". 1126 Message message1 = mFolder.createMessage("1"); 1127 assertFalse(message1.isSet(Flag.SEEN)); 1128 1129 FetchProfile fp = new FetchProfile(); 1130 fp.clear(); 1131 fp.add(FetchProfile.Item.FLAGS); 1132 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID FLAGS\\)", 1133 new String[] { 1134 "* 1 fETCH (uID 1 fLAGS (\\Seen))", 1135 "* 2 fETCH (fLAGS (\\Seen))", 1136 getNextTag(true) + " oK SUCCESS" 1137 }); 1138 1139 // Shouldn't crash 1140 mFolder.fetch(new Message[] { message1 }, fp, null); 1141 1142 // And the message is "SEEN". 1143 assertTrue(message1.isSet(Flag.SEEN)); 1144 } 1145 1146 prepareForAppendTest(MockTransport mock, String response)1147 private ImapMessage prepareForAppendTest(MockTransport mock, String response) throws Exception { 1148 ImapMessage message = (ImapMessage) mFolder.createMessage("initial uid"); 1149 message.setFrom(new Address("me@test.com")); 1150 message.setRecipient(RecipientType.TO, new Address("you@test.com")); 1151 message.setMessageId("<message.id@test.com>"); 1152 message.setFlagDirectlyForTest(Flag.SEEN, true); 1153 message.setBody(new TextBody("Test Body")); 1154 1155 // + go ahead 1156 // * 12345 EXISTS 1157 // OK [APPENDUID 627684530 17] (Success) 1158 1159 mock.expect(getNextTag(false) + 1160 " APPEND \\\"" + FOLDER_ENCODED + "\\\" \\(\\\\SEEN\\) \\{166\\}", 1161 new String[] {"+ gO aHead"}); 1162 1163 mock.expectLiterally("From: me@test.com", NO_REPLY); 1164 mock.expectLiterally("To: you@test.com", NO_REPLY); 1165 mock.expectLiterally("Message-ID: <message.id@test.com>", NO_REPLY); 1166 mock.expectLiterally("Content-Type: text/plain;", NO_REPLY); 1167 mock.expectLiterally(" charset=utf-8", NO_REPLY); 1168 mock.expectLiterally("Content-Transfer-Encoding: base64", NO_REPLY); 1169 mock.expectLiterally("", NO_REPLY); 1170 mock.expectLiterally("VGVzdCBCb2R5", NO_REPLY); 1171 mock.expectLiterally("", new String[] { 1172 "* 7 eXISTS", 1173 getNextTag(true) + " " + response 1174 }); 1175 return message; 1176 } 1177 1178 /** 1179 * Test for APPEND when the response has APPENDUID. 1180 */ testAppendMessages()1181 public void testAppendMessages() throws Exception { 1182 MockTransport mock = openAndInjectMockTransport(); 1183 setupOpenFolder(mock); 1184 mFolder.open(OpenMode.READ_WRITE); 1185 1186 ImapMessage message = prepareForAppendTest(mock, "oK [aPPENDUID 1234567 13] (Success)"); 1187 1188 mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false); 1189 1190 assertEquals("13", message.getUid()); 1191 assertEquals(7, mFolder.getMessageCount()); 1192 } 1193 1194 /** 1195 * Test for APPEND when the response doesn't have APPENDUID. 1196 */ testAppendMessagesNoAppendUid()1197 public void testAppendMessagesNoAppendUid() throws Exception { 1198 MockTransport mock = openAndInjectMockTransport(); 1199 setupOpenFolder(mock); 1200 mFolder.open(OpenMode.READ_WRITE); 1201 1202 ImapMessage message = prepareForAppendTest(mock, "OK Success"); 1203 1204 // First try w/o parenthesis 1205 mock.expectLiterally( 1206 getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id@test.com>", 1207 new String[] { 1208 "* sEARCH 321", 1209 getNextTag(true) + " oK success" 1210 }); 1211 // If that fails, then try w/ parenthesis 1212 mock.expectLiterally( 1213 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id@test.com>)", 1214 new String[] { 1215 "* sEARCH 321", 1216 getNextTag(true) + " oK success" 1217 }); 1218 1219 mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false); 1220 1221 assertEquals("321", message.getUid()); 1222 } 1223 1224 /** 1225 * Test for append failure. 1226 * 1227 * We don't check the response for APPEND. We just SEARCH for the message-id to get the UID. 1228 * If append has failed, the SEARCH command returns no UID, and the UID of the message is left 1229 * unset. 1230 */ testAppendFailure()1231 public void testAppendFailure() throws Exception { 1232 MockTransport mock = openAndInjectMockTransport(); 1233 setupOpenFolder(mock); 1234 mFolder.open(OpenMode.READ_WRITE); 1235 1236 ImapMessage message = prepareForAppendTest(mock, "NO No space left on the server."); 1237 assertEquals("initial uid", message.getUid()); 1238 // First try w/o parenthesis 1239 mock.expectLiterally( 1240 getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id@test.com>", 1241 new String[] { 1242 "* sEARCH", // not found 1243 getNextTag(true) + " oK Search completed." 1244 }); 1245 // If that fails, then try w/ parenthesis 1246 mock.expectLiterally( 1247 getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id@test.com>)", 1248 new String[] { 1249 "* sEARCH", // not found 1250 getNextTag(true) + " oK Search completed." 1251 }); 1252 1253 mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false); 1254 1255 // Shouldn't have changed 1256 assertEquals("initial uid", message.getUid()); 1257 } 1258 testGetAllFolders()1259 public void testGetAllFolders() throws Exception { 1260 MockTransport mock = openAndInjectMockTransport(); 1261 expectLogin(mock); 1262 1263 expectNoop(mock, true); 1264 mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"", 1265 new String[] { 1266 "* lIST (\\HAsNoChildren) \"/\" \"inbox\"", 1267 "* lIST (\\hAsnochildren) \"/\" \"Drafts\"", 1268 "* lIST (\\nOselect) \"/\" \"no select\"", 1269 "* lIST (\\HAsNoChildren) \"/\" \"&ZeVnLIqe-\"", // Japanese folder name 1270 getNextTag(true) + " oK SUCCESS" 1271 }); 1272 Folder[] folders = mStore.updateFolders(); 1273 ImapFolder testFolder; 1274 1275 testFolder = (ImapFolder) folders[0]; 1276 assertEquals("INBOX", testFolder.getName()); 1277 assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS); 1278 1279 testFolder = (ImapFolder) folders[1]; 1280 assertEquals("no select", testFolder.getName()); 1281 assertEquals(0, testFolder.mMailbox.mFlags & SELECTABLE_BITS); 1282 1283 testFolder = (ImapFolder) folders[2]; 1284 assertEquals("\u65E5\u672C\u8A9E", testFolder.getName()); 1285 assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS); 1286 1287 testFolder = (ImapFolder) folders[3]; 1288 assertEquals("Drafts", testFolder.getName()); 1289 assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS); 1290 // TODO test with path prefix 1291 // TODO: Test NO response. 1292 } 1293 testEncodeFolderName()1294 public void testEncodeFolderName() { 1295 // null prefix 1296 assertEquals("", 1297 ImapStore.encodeFolderName("", null)); 1298 assertEquals("a", 1299 ImapStore.encodeFolderName("a", null)); 1300 assertEquals("XYZ", 1301 ImapStore.encodeFolderName("XYZ", null)); 1302 assertEquals("&ZeVnLIqe-", 1303 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", null)); 1304 assertEquals("!&ZeVnLIqe-!", 1305 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", null)); 1306 // empty prefix (same as a null prefix) 1307 assertEquals("", 1308 ImapStore.encodeFolderName("", "")); 1309 assertEquals("a", 1310 ImapStore.encodeFolderName("a", "")); 1311 assertEquals("XYZ", 1312 ImapStore.encodeFolderName("XYZ", "")); 1313 assertEquals("&ZeVnLIqe-", 1314 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", "")); 1315 assertEquals("!&ZeVnLIqe-!", 1316 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", "")); 1317 // defined prefix 1318 assertEquals("[Gmail]/", 1319 ImapStore.encodeFolderName("", "[Gmail]/")); 1320 assertEquals("[Gmail]/a", 1321 ImapStore.encodeFolderName("a", "[Gmail]/")); 1322 assertEquals("[Gmail]/XYZ", 1323 ImapStore.encodeFolderName("XYZ", "[Gmail]/")); 1324 assertEquals("[Gmail]/&ZeVnLIqe-", 1325 ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", "[Gmail]/")); 1326 assertEquals("[Gmail]/!&ZeVnLIqe-!", 1327 ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", "[Gmail]/")); 1328 // Add prefix to special mailbox "INBOX" [case insensitive), no affect 1329 assertEquals("INBOX", 1330 ImapStore.encodeFolderName("INBOX", "[Gmail]/")); 1331 assertEquals("inbox", 1332 ImapStore.encodeFolderName("inbox", "[Gmail]/")); 1333 assertEquals("InBoX", 1334 ImapStore.encodeFolderName("InBoX", "[Gmail]/")); 1335 } 1336 testDecodeFolderName()1337 public void testDecodeFolderName() { 1338 // null prefix 1339 assertEquals("", 1340 ImapStore.decodeFolderName("", null)); 1341 assertEquals("a", 1342 ImapStore.decodeFolderName("a", null)); 1343 assertEquals("XYZ", 1344 ImapStore.decodeFolderName("XYZ", null)); 1345 assertEquals("\u65E5\u672C\u8A9E", 1346 ImapStore.decodeFolderName("&ZeVnLIqe-", null)); 1347 assertEquals("!\u65E5\u672C\u8A9E!", 1348 ImapStore.decodeFolderName("!&ZeVnLIqe-!", null)); 1349 // empty prefix (same as a null prefix) 1350 assertEquals("", 1351 ImapStore.decodeFolderName("", "")); 1352 assertEquals("a", 1353 ImapStore.decodeFolderName("a", "")); 1354 assertEquals("XYZ", 1355 ImapStore.decodeFolderName("XYZ", "")); 1356 assertEquals("\u65E5\u672C\u8A9E", 1357 ImapStore.decodeFolderName("&ZeVnLIqe-", "")); 1358 assertEquals("!\u65E5\u672C\u8A9E!", 1359 ImapStore.decodeFolderName("!&ZeVnLIqe-!", "")); 1360 // defined prefix; prefix found, prefix removed 1361 assertEquals("", 1362 ImapStore.decodeFolderName("[Gmail]/", "[Gmail]/")); 1363 assertEquals("a", 1364 ImapStore.decodeFolderName("[Gmail]/a", "[Gmail]/")); 1365 assertEquals("XYZ", 1366 ImapStore.decodeFolderName("[Gmail]/XYZ", "[Gmail]/")); 1367 assertEquals("\u65E5\u672C\u8A9E", 1368 ImapStore.decodeFolderName("[Gmail]/&ZeVnLIqe-", "[Gmail]/")); 1369 assertEquals("!\u65E5\u672C\u8A9E!", 1370 ImapStore.decodeFolderName("[Gmail]/!&ZeVnLIqe-!", "[Gmail]/")); 1371 // defined prefix; prefix not found, no affect 1372 assertEquals("INBOX/", 1373 ImapStore.decodeFolderName("INBOX/", "[Gmail]/")); 1374 assertEquals("INBOX/a", 1375 ImapStore.decodeFolderName("INBOX/a", "[Gmail]/")); 1376 assertEquals("INBOX/XYZ", 1377 ImapStore.decodeFolderName("INBOX/XYZ", "[Gmail]/")); 1378 assertEquals("INBOX/\u65E5\u672C\u8A9E", 1379 ImapStore.decodeFolderName("INBOX/&ZeVnLIqe-", "[Gmail]/")); 1380 assertEquals("INBOX/!\u65E5\u672C\u8A9E!", 1381 ImapStore.decodeFolderName("INBOX/!&ZeVnLIqe-!", "[Gmail]/")); 1382 } 1383 testEnsurePrefixIsValid()1384 public void testEnsurePrefixIsValid() { 1385 // Test mPathSeparator == null 1386 mStore.mPathSeparator = null; 1387 mStore.mPathPrefix = null; 1388 mStore.ensurePrefixIsValid(); 1389 assertNull(mStore.mPathPrefix); 1390 1391 mStore.mPathPrefix = ""; 1392 mStore.ensurePrefixIsValid(); 1393 assertEquals("", mStore.mPathPrefix); 1394 1395 mStore.mPathPrefix = "foo"; 1396 mStore.ensurePrefixIsValid(); 1397 assertEquals("foo", mStore.mPathPrefix); 1398 1399 mStore.mPathPrefix = "foo."; 1400 mStore.ensurePrefixIsValid(); 1401 assertEquals("foo.", mStore.mPathPrefix); 1402 1403 // Test mPathSeparator == "" 1404 mStore.mPathSeparator = ""; 1405 mStore.mPathPrefix = null; 1406 mStore.ensurePrefixIsValid(); 1407 assertNull(mStore.mPathPrefix); 1408 1409 mStore.mPathPrefix = ""; 1410 mStore.ensurePrefixIsValid(); 1411 assertEquals("", mStore.mPathPrefix); 1412 1413 mStore.mPathPrefix = "foo"; 1414 mStore.ensurePrefixIsValid(); 1415 assertEquals("foo", mStore.mPathPrefix); 1416 1417 mStore.mPathPrefix = "foo."; 1418 mStore.ensurePrefixIsValid(); 1419 assertEquals("foo.", mStore.mPathPrefix); 1420 1421 // Test mPathSeparator is non-empty 1422 mStore.mPathSeparator = "."; 1423 mStore.mPathPrefix = null; 1424 mStore.ensurePrefixIsValid(); 1425 assertNull(mStore.mPathPrefix); 1426 1427 mStore.mPathPrefix = ""; 1428 mStore.ensurePrefixIsValid(); 1429 assertEquals("", mStore.mPathPrefix); 1430 1431 mStore.mPathPrefix = "foo"; 1432 mStore.ensurePrefixIsValid(); 1433 assertEquals("foo.", mStore.mPathPrefix); 1434 1435 // Trailing separator; path separator NOT appended 1436 mStore.mPathPrefix = "foo."; 1437 mStore.ensurePrefixIsValid(); 1438 assertEquals("foo.", mStore.mPathPrefix); 1439 1440 // Trailing punctuation has no affect; path separator still appended 1441 mStore.mPathPrefix = "foo/"; 1442 mStore.ensurePrefixIsValid(); 1443 assertEquals("foo/.", mStore.mPathPrefix); 1444 } 1445 testOpen()1446 public void testOpen() throws Exception { 1447 MockTransport mock = openAndInjectMockTransport(); 1448 expectLogin(mock); 1449 1450 final Folder folder = mStore.getFolder("test"); 1451 1452 // Not exist 1453 mock.expect(getNextTag(false) + " SELECT \\\"test\\\"", 1454 new String[] { 1455 getNextTag(true) + " nO no such mailbox" 1456 }); 1457 try { 1458 folder.open(OpenMode.READ_WRITE); 1459 fail(); 1460 } catch (MessagingException expected) { 1461 } 1462 1463 // READ-WRITE 1464 expectNoop(mock, true); // Need it because we reuse the connection. 1465 mock.expect(getNextTag(false) + " SELECT \\\"test\\\"", 1466 new String[] { 1467 "* 1 eXISTS", 1468 getNextTag(true) + " oK [rEAD-wRITE]" 1469 }); 1470 1471 folder.open(OpenMode.READ_WRITE); 1472 assertTrue(folder.exists()); 1473 assertEquals(1, folder.getMessageCount()); 1474 assertEquals(OpenMode.READ_WRITE, folder.getMode()); 1475 1476 assertTrue(folder.isOpen()); 1477 folder.close(false); 1478 assertFalse(folder.isOpen()); 1479 1480 // READ-ONLY 1481 expectNoop(mock, true); // Need it because we reuse the connection. 1482 mock.expect(getNextTag(false) + " SELECT \\\"test\\\"", 1483 new String[] { 1484 "* 2 eXISTS", 1485 getNextTag(true) + " oK [rEAD-oNLY]" 1486 }); 1487 1488 folder.open(OpenMode.READ_WRITE); 1489 assertTrue(folder.exists()); 1490 assertEquals(2, folder.getMessageCount()); 1491 assertEquals(OpenMode.READ_ONLY, folder.getMode()); 1492 1493 // Try to re-open as read-write. Should send SELECT again. 1494 expectNoop(mock, true); // Need it because we reuse the connection. 1495 mock.expect(getNextTag(false) + " SELECT \\\"test\\\"", 1496 new String[] { 1497 "* 15 eXISTS", 1498 getNextTag(true) + " oK selected" 1499 }); 1500 1501 folder.open(OpenMode.READ_WRITE); 1502 assertTrue(folder.exists()); 1503 assertEquals(15, folder.getMessageCount()); 1504 assertEquals(OpenMode.READ_WRITE, folder.getMode()); 1505 } 1506 testExists()1507 public void testExists() throws Exception { 1508 MockTransport mock = openAndInjectMockTransport(); 1509 expectLogin(mock); 1510 1511 // Folder exists 1512 Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E"); 1513 mock.expect(getNextTag(false) + " STATUS \\\"&ZeVnLIqe-\\\" \\(UIDVALIDITY\\)", 1514 new String[] { 1515 "* sTATUS \"&ZeVnLIqe-\" (mESSAGES 10)", 1516 getNextTag(true) + " oK SUCCESS" 1517 }); 1518 1519 assertTrue(folder.exists()); 1520 1521 // Connection verification 1522 expectNoop(mock, true); 1523 1524 // Doesn't exist 1525 folder = mStore.getFolder("no such folder"); 1526 mock.expect(getNextTag(false) + " STATUS \\\"no such folder\\\" \\(UIDVALIDITY\\)", 1527 new String[] { 1528 getNextTag(true) + " NO No such folder!" 1529 }); 1530 1531 assertFalse(folder.exists()); 1532 } 1533 testCreate()1534 public void testCreate() throws Exception { 1535 MockTransport mock = openAndInjectMockTransport(); 1536 expectLogin(mock); 1537 1538 // Success 1539 Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E"); 1540 1541 assertTrue(folder.canCreate(FolderType.HOLDS_MESSAGES)); 1542 1543 mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"", 1544 new String[] { 1545 getNextTag(true) + " oK Success" 1546 }); 1547 1548 assertTrue(folder.create(FolderType.HOLDS_MESSAGES)); 1549 1550 // Connection verification 1551 expectNoop(mock, true); 1552 1553 // Failure 1554 mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"", 1555 new String[] { 1556 getNextTag(true) + " nO Can't create folder" 1557 }); 1558 1559 assertFalse(folder.create(FolderType.HOLDS_MESSAGES)); 1560 } 1561 setupCopyMessages(boolean withUidPlus)1562 private void setupCopyMessages(boolean withUidPlus) throws Exception { 1563 mCopyMock = openAndInjectMockTransport(); 1564 setupOpenFolder(mCopyMock, new String[] {"* iD nIL", "oK"}, "rEAD-wRITE", withUidPlus); 1565 mFolder.open(OpenMode.READ_WRITE); 1566 1567 mCopyToFolder = mStore.getFolder("\u65E5\u672C\u8A9E"); 1568 Message m1 = mFolder.createMessage("11"); 1569 m1.setMessageId("<4D8978AE.0000005D@m58.foo.com>"); 1570 Message m2 = mFolder.createMessage("12"); 1571 m2.setMessageId("<549373104MSOSI1:145OSIMS@bar.com>"); 1572 mCopyMessages = new Message[] { m1, m2 }; 1573 } 1574 1575 /** 1576 * Returns the pattern for the IMAP request to copy messages. 1577 */ getCopyMessagesPattern()1578 private String getCopyMessagesPattern() { 1579 return getNextTag(false) + " UID COPY 11\\,12 \\\"&ZeVnLIqe-\\\""; 1580 } 1581 1582 /** 1583 * Returns the pattern for the IMAP request to search for messages based on Message-Id. 1584 */ getSearchMessagesPattern(String messageId)1585 private String getSearchMessagesPattern(String messageId) { 1586 return getNextTag(false) + " UID SEARCH HEADER Message-Id \"" + messageId + "\""; 1587 } 1588 1589 /** 1590 * Counts the number of times the callback methods are invoked. 1591 */ 1592 private static class MessageUpdateCallbackCounter implements Folder.MessageUpdateCallbacks { 1593 int messageNotFoundCalled; 1594 int messageUidChangeCalled; 1595 1596 @Override onMessageNotFound(Message message)1597 public void onMessageNotFound(Message message) { 1598 ++messageNotFoundCalled; 1599 } 1600 @Override onMessageUidChange(Message message, String newUid)1601 public void onMessageUidChange(Message message, String newUid) { 1602 ++messageUidChangeCalled; 1603 } 1604 } 1605 1606 // TODO Test additional degenerate cases; src msg not found, ... 1607 // Golden case; successful copy with UIDCOPY result testCopyMessages1()1608 public void testCopyMessages1() throws Exception { 1609 setupCopyMessages(true); 1610 mCopyMock.expect(getCopyMessagesPattern(), 1611 new String[] { 1612 "* Ok COPY in progress", 1613 getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed" 1614 }); 1615 1616 MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter(); 1617 mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb); 1618 1619 assertEquals(0, cb.messageNotFoundCalled); 1620 assertEquals(2, cb.messageUidChangeCalled); 1621 } 1622 1623 // Degenerate case; NO, un-tagged response works testCopyMessages2()1624 public void testCopyMessages2() throws Exception { 1625 setupCopyMessages(true); 1626 mCopyMock.expect(getCopyMessagesPattern(), 1627 new String[] { 1628 "* No Some error occured during the copy", 1629 getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed" 1630 }); 1631 1632 MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter(); 1633 mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb); 1634 1635 assertEquals(0, cb.messageNotFoundCalled); 1636 assertEquals(2, cb.messageUidChangeCalled); 1637 } 1638 1639 // Degenerate case; NO, tagged response throws MessagingException testCopyMessages3()1640 public void testCopyMessages3() throws Exception { 1641 try { 1642 setupCopyMessages(false); 1643 mCopyMock.expect(getCopyMessagesPattern(), 1644 new String[] { 1645 getNextTag(true) + " No copy did not finish" 1646 }); 1647 1648 mFolder.copyMessages(mCopyMessages, mCopyToFolder, null); 1649 1650 fail("MessagingException expected."); 1651 } catch (MessagingException expected) { 1652 } 1653 } 1654 1655 // Degenerate case; BAD, un-tagged response throws MessagingException testCopyMessages4()1656 public void testCopyMessages4() throws Exception { 1657 try { 1658 setupCopyMessages(true); 1659 mCopyMock.expect(getCopyMessagesPattern(), 1660 new String[] { 1661 "* BAD failed for some reason", 1662 getNextTag(true) + " Ok copy completed" 1663 }); 1664 1665 mFolder.copyMessages(mCopyMessages, mCopyToFolder, null); 1666 1667 fail("MessagingException expected."); 1668 } catch (MessagingException expected) { 1669 } 1670 } 1671 1672 // Degenerate case; BAD, tagged response throws MessagingException testCopyMessages5()1673 public void testCopyMessages5() throws Exception { 1674 try { 1675 setupCopyMessages(false); 1676 mCopyMock.expect(getCopyMessagesPattern(), 1677 new String[] { 1678 getNextTag(true) + " BaD copy completed" 1679 }); 1680 1681 mFolder.copyMessages(mCopyMessages, mCopyToFolder, null); 1682 1683 fail("MessagingException expected."); 1684 } catch (MessagingException expected) { 1685 } 1686 } 1687 1688 // Golden case; successful copy getting UIDs via search testCopyMessages6()1689 public void testCopyMessages6() throws Exception { 1690 setupCopyMessages(false); 1691 mCopyMock.expect(getCopyMessagesPattern(), 1692 new String[] { 1693 getNextTag(true) + " oK UID COPY completed", 1694 }); 1695 // New connection, so, we need to login again & the tag count gets reset 1696 int saveTag = resetTag(); 1697 expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false); 1698 // Select destination folder 1699 expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE"); 1700 // Perform searches 1701 mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"), 1702 new String[] { 1703 "* SeArCh 777", 1704 getNextTag(true) + " oK UID SEARCH completed (1 msgs in 3.14159 secs)", 1705 }); 1706 mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"), 1707 new String[] { 1708 "* sEaRcH 1818", 1709 getNextTag(true) + " oK UID SEARCH completed (1 msgs in 2.71828 secs)", 1710 }); 1711 // Resume commands on the initial connection 1712 resetTag(saveTag); 1713 // Select the original folder 1714 expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE"); 1715 1716 MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter(); 1717 mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb); 1718 1719 assertEquals(0, cb.messageNotFoundCalled); 1720 assertEquals(2, cb.messageUidChangeCalled); 1721 } 1722 1723 // Degenerate case; searches turn up nothing testCopyMessages7()1724 public void testCopyMessages7() throws Exception { 1725 setupCopyMessages(false); 1726 mCopyMock.expect(getCopyMessagesPattern(), 1727 new String[] { 1728 getNextTag(true) + " oK UID COPY completed", 1729 }); 1730 // New connection, so, we need to login again & the tag count gets reset 1731 int saveTag = resetTag(); 1732 expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false); 1733 // Select destination folder 1734 expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE"); 1735 // Perform searches 1736 mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"), 1737 new String[] { 1738 "* SeArCh", 1739 getNextTag(true) + " oK UID SEARCH completed (0 msgs in 6.02214 secs)", 1740 }); 1741 mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"), 1742 new String[] { 1743 "* sEaRcH", 1744 getNextTag(true) + " oK UID SEARCH completed (0 msgs in 2.99792 secs)", 1745 }); 1746 // Resume commands on the initial connection 1747 resetTag(saveTag); 1748 // Select the original folder 1749 expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE"); 1750 1751 MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter(); 1752 mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb); 1753 1754 assertEquals(0, cb.messageNotFoundCalled); 1755 assertEquals(0, cb.messageUidChangeCalled); 1756 } 1757 1758 // Degenerate case; search causes an exception; must be eaten testCopyMessages8()1759 public void testCopyMessages8() throws Exception { 1760 setupCopyMessages(false); 1761 mCopyMock.expect(getCopyMessagesPattern(), 1762 new String[] { 1763 getNextTag(true) + " oK UID COPY completed", 1764 }); 1765 // New connection, so, we need to login again & the tag count gets reset 1766 int saveTag = resetTag(); 1767 expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false); 1768 // Select destination folder 1769 expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE"); 1770 // Perform searches 1771 mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"), 1772 new String[] { 1773 getNextTag(true) + " BaD search failed" 1774 }); 1775 mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"), 1776 new String[] { 1777 getNextTag(true) + " BaD search failed" 1778 }); 1779 // Resume commands on the initial connection 1780 resetTag(saveTag); 1781 // Select the original folder 1782 expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE"); 1783 1784 MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter(); 1785 mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb); 1786 1787 assertEquals(0, cb.messageNotFoundCalled); 1788 assertEquals(0, cb.messageUidChangeCalled); 1789 } 1790 testGetUnreadMessageCount()1791 public void testGetUnreadMessageCount() throws Exception { 1792 MockTransport mock = openAndInjectMockTransport(); 1793 setupOpenFolder(mock); 1794 mFolder.open(OpenMode.READ_WRITE); 1795 1796 mock.expect(getNextTag(false) + " STATUS \\\"" + FOLDER_ENCODED + "\\\" \\(UNSEEN\\)", 1797 new String[] { 1798 "* sTATUS \"" + FOLDER_ENCODED + "\" (X 1 uNSEEN 123)", 1799 getNextTag(true) + " oK copy completed" 1800 }); 1801 1802 assertEquals(123, mFolder.getUnreadMessageCount()); 1803 } 1804 testExpunge()1805 public void testExpunge() throws Exception { 1806 MockTransport mock = openAndInjectMockTransport(); 1807 setupOpenFolder(mock); 1808 mFolder.open(OpenMode.READ_WRITE); 1809 1810 mock.expect(getNextTag(false) + " EXPUNGE", 1811 new String[] { 1812 getNextTag(true) + " oK success" 1813 }); 1814 1815 mFolder.expunge(); 1816 1817 // TODO: Test NO response. (permission denied) 1818 } 1819 testSetFlags()1820 public void testSetFlags() throws Exception { 1821 MockTransport mock = openAndInjectMockTransport(); 1822 setupOpenFolder(mock); 1823 mFolder.open(OpenMode.READ_WRITE); 1824 1825 Message[] messages = new Message[] { 1826 mFolder.createMessage("11"), 1827 mFolder.createMessage("12"), 1828 }; 1829 1830 // Set 1831 mock.expect( 1832 getNextTag(false) + " UID STORE 11\\,12 \\+FLAGS.SILENT \\(\\\\FLAGGED \\\\SEEN\\)", 1833 new String[] { 1834 getNextTag(true) + " oK success" 1835 }); 1836 mFolder.setFlags(messages, new Flag[] {Flag.FLAGGED, Flag.SEEN}, true); 1837 1838 // Clear 1839 mock.expect( 1840 getNextTag(false) + " UID STORE 11\\,12 \\-FLAGS.SILENT \\(\\\\DELETED\\)", 1841 new String[] { 1842 getNextTag(true) + " oK success" 1843 }); 1844 mFolder.setFlags(messages, new Flag[] {Flag.DELETED}, false); 1845 1846 // TODO: Test NO response. (src message not found) 1847 } 1848 testSearchForUids()1849 public void testSearchForUids() throws Exception { 1850 MockTransport mock = openAndInjectMockTransport(); 1851 setupOpenFolder(mock); 1852 mFolder.open(OpenMode.READ_WRITE); 1853 1854 // Single results 1855 mock.expect( 1856 getNextTag(false) + " UID SEARCH X", 1857 new String[] { 1858 "* sEARCH 1", 1859 getNextTag(true) + " oK success" 1860 }); 1861 MoreAsserts.assertEquals(new String[] { 1862 "1" 1863 }, mFolder.searchForUids("X")); 1864 1865 // Multiple results, including SEARCH with no UIDs. 1866 mock.expect( 1867 getNextTag(false) + " UID SEARCH UID 123", 1868 new String[] { 1869 "* sEARCH 123 4 567", 1870 "* search", 1871 "* sEARCH 0", 1872 "* SEARCH", 1873 "* sEARCH 100 200 300", 1874 getNextTag(true) + " oK success" 1875 }); 1876 MoreAsserts.assertEquals(new String[] { 1877 "123", "4", "567", "0", "100", "200", "300" 1878 }, mFolder.searchForUids("UID 123")); 1879 1880 // NO result 1881 mock.expect( 1882 getNextTag(false) + " UID SEARCH SOME CRITERIA", 1883 new String[] { 1884 getNextTag(true) + " nO not found" 1885 }); 1886 MoreAsserts.assertEquals(new String[] { 1887 }, mFolder.searchForUids("SOME CRITERIA")); 1888 1889 // OK result, but result is empty. (Probably against RFC) 1890 mock.expect( 1891 getNextTag(false) + " UID SEARCH SOME CRITERIA", 1892 new String[] { 1893 getNextTag(true) + " oK success" 1894 }); 1895 MoreAsserts.assertEquals(new String[] { 1896 }, mFolder.searchForUids("SOME CRITERIA")); 1897 1898 // OK result with empty search response. 1899 mock.expect( 1900 getNextTag(false) + " UID SEARCH SOME CRITERIA", 1901 new String[] { 1902 "* search", 1903 getNextTag(true) + " oK success" 1904 }); 1905 MoreAsserts.assertEquals(new String[] { 1906 }, mFolder.searchForUids("SOME CRITERIA")); 1907 } 1908 1909 testGetMessage()1910 public void testGetMessage() throws Exception { 1911 MockTransport mock = openAndInjectMockTransport(); 1912 setupOpenFolder(mock); 1913 mFolder.open(OpenMode.READ_WRITE); 1914 1915 // Found 1916 mock.expect( 1917 getNextTag(false) + " UID SEARCH UID 123", 1918 new String[] { 1919 "* sEARCH 123", 1920 getNextTag(true) + " oK success" 1921 }); 1922 assertEquals("123", mFolder.getMessage("123").getUid()); 1923 1924 // Not found 1925 mock.expect( 1926 getNextTag(false) + " UID SEARCH UID 123", 1927 new String[] { 1928 getNextTag(true) + " nO not found" 1929 }); 1930 assertNull(mFolder.getMessage("123")); 1931 } 1932 1933 /** Test for getMessages(int, int, MessageRetrievalListener) */ testGetMessages1()1934 public void testGetMessages1() throws Exception { 1935 MockTransport mock = openAndInjectMockTransport(); 1936 setupOpenFolder(mock); 1937 mFolder.open(OpenMode.READ_WRITE); 1938 1939 // Found 1940 mock.expect( 1941 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED", 1942 new String[] { 1943 "* sEARCH 3 4", 1944 getNextTag(true) + " oK success" 1945 }); 1946 1947 checkMessageUids(new String[] {"3", "4"}, mFolder.getMessages(3, 5, null)); 1948 1949 // Not found 1950 mock.expect( 1951 getNextTag(false) + " UID SEARCH 3:5 NOT DELETED", 1952 new String[] { 1953 getNextTag(true) + " nO not found" 1954 }); 1955 1956 checkMessageUids(new String[] {}, mFolder.getMessages(3, 5, null)); 1957 } 1958 1959 /** 1960 * Test for getMessages(String[] uids, MessageRetrievalListener) where uids != null. 1961 * (testGetMessages3() covers the case where uids == null.) 1962 */ testGetMessages2()1963 public void testGetMessages2() throws Exception { 1964 MockTransport mock = openAndInjectMockTransport(); 1965 setupOpenFolder(mock); 1966 mFolder.open(OpenMode.READ_WRITE); 1967 1968 // No command will be sent 1969 checkMessageUids(new String[] {"3", "4", "5"}, 1970 mFolder.getMessages(new String[] {"3", "4", "5"}, null)); 1971 1972 checkMessageUids(new String[] {}, 1973 mFolder.getMessages(new String[] {}, null)); 1974 } 1975 checkMessageUids(String[] expectedUids, Message[] actualMessages)1976 private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) { 1977 ArrayList<String> list = new ArrayList<String>(); 1978 for (Message m : actualMessages) { 1979 list.add(m.getUid()); 1980 } 1981 MoreAsserts.assertEquals(expectedUids, list.toArray(new String[0]) ); 1982 } 1983 1984 /** 1985 * Test for {@link ImapStore#getConnection} 1986 */ testGetConnection()1987 public void testGetConnection() throws Exception { 1988 MockTransport mock = openAndInjectMockTransport(); 1989 1990 // Start: No pooled connections. 1991 assertEquals(0, mStore.getConnectionPoolForTest().size()); 1992 1993 // Get 1st connection. 1994 final ImapConnection con1 = mStore.getConnection(); 1995 assertNotNull(con1); 1996 assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed. 1997 assertFalse(con1.isTransportOpenForTest()); // Transport not open yet. 1998 1999 // Open con1 2000 expectLogin(mock); 2001 con1.open(); 2002 assertTrue(con1.isTransportOpenForTest()); 2003 2004 // Get 2nd connection. 2005 final ImapConnection con2 = mStore.getConnection(); 2006 assertNotNull(con2); 2007 assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed. 2008 assertFalse(con2.isTransportOpenForTest()); // Transport not open yet. 2009 2010 // con1 != con2 2011 assertNotSame(con1, con2); 2012 2013 // New connection, so, we need to login again & the tag count gets reset 2014 int saveTag = resetTag(); 2015 2016 // Open con2 2017 expectLogin(mock); 2018 con2.open(); 2019 assertTrue(con1.isTransportOpenForTest()); 2020 2021 // Now we have two open connections: con1 and con2 2022 2023 // Save con1 in the pool. 2024 mStore.poolConnection(con1); 2025 assertEquals(1, mStore.getConnectionPoolForTest().size()); 2026 2027 // Get another connection. Should get con1, after verifying the connection. 2028 saveTag = resetTag(saveTag); 2029 mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + " oK success"}); 2030 2031 final ImapConnection con1b = mStore.getConnection(); 2032 assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool 2033 assertSame(con1, con1b); 2034 assertTrue(con1.isTransportOpenForTest()); // We opened it. 2035 2036 // Save con2. 2037 mStore.poolConnection(con2); 2038 assertEquals(1, mStore.getConnectionPoolForTest().size()); 2039 2040 // Resume con2 tags ... 2041 resetTag(saveTag); 2042 2043 // Try to get connection, but this time, connection gets closed. 2044 mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + "* bYE bye"}); 2045 final ImapConnection con3 = mStore.getConnection(); 2046 assertNotNull(con3); 2047 assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool 2048 2049 // It should be a new connection. 2050 assertNotSame(con1, con3); 2051 assertNotSame(con2, con3); 2052 } 2053 testCheckSettings()2054 public void testCheckSettings() throws Exception { 2055 MockTransport mock = openAndInjectMockTransport(); 2056 2057 expectLogin(mock); 2058 mStore.checkSettings(); 2059 2060 resetTag(); 2061 expectLogin(mock, false, false, false, 2062 new String[] {"* iD nIL", "oK"}, "nO authentication failed"); 2063 try { 2064 mStore.checkSettings(); 2065 fail(); 2066 } catch (MessagingException expected) { 2067 } 2068 } 2069 2070 // Compatibility tests... 2071 2072 /** 2073 * Getting an ALERT with a % mark in the message, which crashed the old parser. 2074 */ testQuotaAlert()2075 public void testQuotaAlert() throws Exception { 2076 MockTransport mock = openAndInjectMockTransport(); 2077 expectLogin(mock); 2078 2079 // Success 2080 Folder folder = mStore.getFolder("INBOX"); 2081 2082 // The following response was copied from an actual bug... 2083 mock.expect(getNextTag(false) + " SELECT \"INBOX\"", new String[] { 2084 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk $Forwarded Junk" + 2085 " $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old)", 2086 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk" + 2087 " $Forwarded Junk $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old \\*)]", 2088 "* 6406 EXISTS", 2089 "* 0 RECENT", 2090 "* OK [UNSEEN 5338]", 2091 "* OK [UIDVALIDITY 1055957975]", 2092 "* OK [UIDNEXT 449625]", 2093 "* NO [ALERT] Mailbox is at 98% of quota", 2094 getNextTag(true) + " OK [READ-WRITE] Completed"}); 2095 folder.open(OpenMode.READ_WRITE); // shouldn't crash. 2096 assertEquals(6406, folder.getMessageCount()); 2097 } 2098 2099 /** 2100 * Apparently some servers send a size in the wrong format. e.g. 123E 2101 */ testFetchBodyStructureMalformed()2102 public void testFetchBodyStructureMalformed() throws Exception { 2103 final MockTransport mock = openAndInjectMockTransport(); 2104 setupOpenFolder(mock); 2105 mFolder.open(OpenMode.READ_WRITE); 2106 final Message message = mFolder.createMessage("1"); 2107 2108 final FetchProfile fp = new FetchProfile(); 2109 fp.add(FetchProfile.Item.STRUCTURE); 2110 mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", 2111 new String[] { 2112 "* 9 FETCH (UID 1 BODYSTRUCTURE (\"TEXT\" \"PLAIN\" ()" + 2113 " NIL NIL NIL 123E 3))", // 123E isn't a number! 2114 getNextTag(true) + " OK SUCCESS" 2115 }); 2116 mFolder.fetch(new Message[] { message }, fp, null); 2117 2118 // Check mime structure... 2119 MoreAsserts.assertEquals( 2120 new String[] {"text/plain"}, 2121 message.getHeader("Content-Type") 2122 ); 2123 assertNull(message.getHeader("Content-Transfer-Encoding")); 2124 assertNull(message.getHeader("Content-ID")); 2125 2126 // Doesn't have size=xxx 2127 assertNull(message.getHeader("Content-Disposition")); 2128 } 2129 2130 /** 2131 * Folder name with special chars in it. 2132 * 2133 * Gmail puts the folder name in the OK response, which crashed the old parser if there's a 2134 * special char in the folder name. 2135 */ testFolderNameWithSpecialChars()2136 public void testFolderNameWithSpecialChars() throws Exception { 2137 final String FOLDER_1 = "@u88**%_St"; 2138 final String FOLDER_1_QUOTED = Pattern.quote(FOLDER_1); 2139 final String FOLDER_2 = "folder test_06"; 2140 2141 MockTransport mock = openAndInjectMockTransport(); 2142 expectLogin(mock); 2143 2144 // List folders. 2145 expectNoop(mock, true); 2146 mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"", 2147 new String[] { 2148 "* LIST () \"/\" \"" + FOLDER_1 + "\"", 2149 "* LIST () \"/\" \"" + FOLDER_2 + "\"", 2150 getNextTag(true) + " OK SUCCESS" 2151 }); 2152 final Folder[] folders = mStore.updateFolders(); 2153 2154 ArrayList<String> list = new ArrayList<String>(); 2155 for (Folder f : folders) { 2156 list.add(f.getName()); 2157 } 2158 MoreAsserts.assertEquals( 2159 new String[] {"INBOX", FOLDER_2, FOLDER_1}, 2160 list.toArray(new String[0]) 2161 ); 2162 2163 // Try to open the folders. 2164 expectNoop(mock, true); 2165 mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_1_QUOTED + "\"", new String[] { 2166 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)", 2167 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]", 2168 "* 0 EXISTS", 2169 "* 0 RECENT", 2170 "* OK [UNSEEN 0]", 2171 "* OK [UIDNEXT 1]", 2172 getNextTag(true) + " OK [READ-WRITE] " + FOLDER_1}); 2173 folders[2].open(OpenMode.READ_WRITE); 2174 folders[2].close(false); 2175 2176 expectNoop(mock, true); 2177 mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_2 + "\"", new String[] { 2178 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)", 2179 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]", 2180 "* 0 EXISTS", 2181 "* 0 RECENT", 2182 "* OK [UNSEEN 0]", 2183 "* OK [UIDNEXT 1]", 2184 getNextTag(true) + " OK [READ-WRITE] " + FOLDER_2}); 2185 folders[1].open(OpenMode.READ_WRITE); 2186 folders[1].close(false); 2187 } 2188 2189 /** 2190 * Callback for {@link #runAndExpectMessagingException}. 2191 */ 2192 private interface RunAndExpectMessagingExceptionTarget { run(MockTransport mockTransport)2193 public void run(MockTransport mockTransport) throws Exception; 2194 } 2195 2196 /** 2197 * Set up the usual mock transport, open the folder, 2198 * run {@link RunAndExpectMessagingExceptionTarget} and make sure a {@link MessagingException} 2199 * is thrown. 2200 */ runAndExpectMessagingException(RunAndExpectMessagingExceptionTarget target)2201 private void runAndExpectMessagingException(RunAndExpectMessagingExceptionTarget target) 2202 throws Exception { 2203 try { 2204 final MockTransport mockTransport = openAndInjectMockTransport(); 2205 setupOpenFolder(mockTransport); 2206 mFolder.open(OpenMode.READ_WRITE); 2207 2208 target.run(mockTransport); 2209 2210 fail("MessagingException expected."); 2211 } catch (MessagingException expected) { 2212 } 2213 } 2214 2215 /** 2216 * Make sure that IOExceptions are always converted to MessagingException. 2217 */ testFetchIOException()2218 public void testFetchIOException() throws Exception { 2219 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2220 @Override 2221 public void run(MockTransport mockTransport) throws Exception { 2222 mockTransport.expectIOException(); 2223 2224 final Message message = mFolder.createMessage("1"); 2225 final FetchProfile fp = new FetchProfile(); 2226 fp.add(FetchProfile.Item.STRUCTURE); 2227 2228 mFolder.fetch(new Message[] { message }, fp, null); 2229 } 2230 }); 2231 } 2232 2233 /** 2234 * Make sure that IOExceptions are always converted to MessagingException. 2235 */ testUnreadMessageCountIOException()2236 public void testUnreadMessageCountIOException() throws Exception { 2237 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2238 @Override 2239 public void run(MockTransport mockTransport) throws Exception { 2240 mockTransport.expectIOException(); 2241 2242 mFolder.getUnreadMessageCount(); 2243 } 2244 }); 2245 } 2246 2247 /** 2248 * Make sure that IOExceptions are always converted to MessagingException. 2249 */ testCopyMessagesIOException()2250 public void testCopyMessagesIOException() throws Exception { 2251 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2252 @Override 2253 public void run(MockTransport mockTransport) throws Exception { 2254 mockTransport.expectIOException(); 2255 2256 final Message message = mFolder.createMessage("1"); 2257 final Folder folder = mStore.getFolder("test"); 2258 2259 mFolder.copyMessages(new Message[] { message }, folder, null); 2260 } 2261 }); 2262 } 2263 2264 /** 2265 * Make sure that IOExceptions are always converted to MessagingException. 2266 */ testSearchForUidsIOException()2267 public void testSearchForUidsIOException() throws Exception { 2268 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2269 @Override 2270 public void run(MockTransport mockTransport) throws Exception { 2271 mockTransport.expectIOException(); 2272 2273 mFolder.getMessage("uid"); 2274 } 2275 }); 2276 } 2277 2278 /** 2279 * Make sure that IOExceptions are always converted to MessagingException. 2280 */ testExpungeIOException()2281 public void testExpungeIOException() throws Exception { 2282 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2283 @Override 2284 public void run(MockTransport mockTransport) throws Exception { 2285 mockTransport.expectIOException(); 2286 2287 mFolder.expunge(); 2288 } 2289 }); 2290 } 2291 2292 /** 2293 * Make sure that IOExceptions are always converted to MessagingException. 2294 */ testOpenIOException()2295 public void testOpenIOException() throws Exception { 2296 runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() { 2297 @Override 2298 public void run(MockTransport mockTransport) throws Exception { 2299 mockTransport.expectIOException(); 2300 final Folder folder = mStore.getFolder("test"); 2301 folder.open(OpenMode.READ_WRITE); 2302 } 2303 }); 2304 } 2305 2306 /** Creates a folder & mailbox */ createFolder(long id, String displayName, String serverId, char delimiter)2307 private ImapFolder createFolder(long id, String displayName, String serverId, char delimiter) { 2308 ImapFolder folder = new ImapFolder(null, serverId); 2309 Mailbox mailbox = new Mailbox(); 2310 mailbox.mId = id; 2311 mailbox.mDisplayName = displayName; 2312 mailbox.mServerId = serverId; 2313 mailbox.mDelimiter = delimiter; 2314 mailbox.mFlags = 0xAAAAAAA8; 2315 folder.mMailbox = mailbox; 2316 return folder; 2317 } 2318 2319 /** Tests creating folder hierarchies */ testCreateHierarchy()2320 public void testCreateHierarchy() { 2321 HashMap<String, ImapFolder> testMap = new HashMap<String, ImapFolder>(); 2322 2323 // Create hierarchy 2324 // |-INBOX 2325 // | +-b 2326 // |-a 2327 // | |-b 2328 // | |-c 2329 // | +-d 2330 // | +-b 2331 // | +-b 2332 // +-g 2333 ImapFolder[] folders = { 2334 createFolder(1L, "INBOX", "INBOX", '/'), 2335 createFolder(2L, "b", "INBOX/b", '/'), 2336 createFolder(3L, "a", "a", '/'), 2337 createFolder(4L, "b", "a/b", '/'), 2338 createFolder(5L, "c", "a/c", '/'), 2339 createFolder(6L, "d", "a/d", '/'), 2340 createFolder(7L, "b", "a/d/b", '/'), 2341 createFolder(8L, "b", "a/d/b/b", '/'), 2342 createFolder(9L, "g", "g", '/'), 2343 }; 2344 for (ImapFolder folder : folders) { 2345 testMap.put(folder.getName(), folder); 2346 } 2347 2348 ImapStore.createHierarchy(testMap); 2349 // 'INBOX' 2350 assertEquals(-1L, folders[0].mMailbox.mParentKey); 2351 assertEquals(0xAAAAAAAB, folders[0].mMailbox.mFlags); 2352 // 'INBOX/b' 2353 assertEquals(1L, folders[1].mMailbox.mParentKey); 2354 assertEquals(0xAAAAAAA8, folders[1].mMailbox.mFlags); 2355 // 'a' 2356 assertEquals(-1L, folders[2].mMailbox.mParentKey); 2357 assertEquals(0xAAAAAAAB, folders[2].mMailbox.mFlags); 2358 // 'a/b' 2359 assertEquals(3L, folders[3].mMailbox.mParentKey); 2360 assertEquals(0xAAAAAAA8, folders[3].mMailbox.mFlags); 2361 // 'a/c' 2362 assertEquals(3L, folders[4].mMailbox.mParentKey); 2363 assertEquals(0xAAAAAAA8, folders[4].mMailbox.mFlags); 2364 // 'a/d' 2365 assertEquals(3L, folders[5].mMailbox.mParentKey); 2366 assertEquals(0xAAAAAAAB, folders[5].mMailbox.mFlags); 2367 // 'a/d/b' 2368 assertEquals(6L, folders[6].mMailbox.mParentKey); 2369 assertEquals(0xAAAAAAAB, folders[6].mMailbox.mFlags); 2370 // 'a/d/b/b' 2371 assertEquals(7L, folders[7].mMailbox.mParentKey); 2372 assertEquals(0xAAAAAAA8, folders[7].mMailbox.mFlags); 2373 // 'g' 2374 assertEquals(-1L, folders[8].mMailbox.mParentKey); 2375 assertEquals(0xAAAAAAA8, folders[8].mMailbox.mFlags); 2376 } 2377 } 2378