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