LegacyConversionsTests.java revision 5b0a12c199198870161876996296b1262c17408e
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email; 18 19import com.android.email.mail.Address; 20import com.android.email.mail.BodyPart; 21import com.android.email.mail.Flag; 22import com.android.email.mail.Message; 23import com.android.email.mail.MessageTestUtils; 24import com.android.email.mail.MessagingException; 25import com.android.email.mail.Part; 26import com.android.email.mail.Message.RecipientType; 27import com.android.email.mail.MessageTestUtils.MessageBuilder; 28import com.android.email.mail.MessageTestUtils.MultipartBuilder; 29import com.android.email.mail.internet.MimeBodyPart; 30import com.android.email.mail.internet.MimeHeader; 31import com.android.email.mail.internet.MimeMessage; 32import com.android.email.mail.internet.MimeUtility; 33import com.android.email.mail.internet.TextBody; 34import com.android.email.provider.EmailContent; 35import com.android.email.provider.EmailProvider; 36import com.android.email.provider.ProviderTestUtils; 37import com.android.email.provider.EmailContent.Attachment; 38 39import android.content.ContentUris; 40import android.content.Context; 41import android.database.Cursor; 42import android.net.Uri; 43import android.test.ProviderTestCase2; 44 45import java.io.IOException; 46import java.util.ArrayList; 47import java.util.Date; 48 49/** 50 * Tests of the Legacy Conversions code (used by MessagingController). 51 * 52 * NOTE: It would probably make sense to rewrite this using a MockProvider, instead of the 53 * ProviderTestCase (which is a real provider running on a temp database). This would be more of 54 * a true "unit test". 55 * 56 * You can run this entire test case with: 57 * runtest -c com.android.email.LegacyConversionsTests email 58 */ 59public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> { 60 61 private static final String UID = "UID.12345678"; 62 private static final String SENDER = "sender@android.com"; 63 private static final String RECIPIENT_TO = "recipient-to@android.com"; 64 private static final String RECIPIENT_CC = "recipient-cc@android.com"; 65 private static final String RECIPIENT_BCC = "recipient-bcc@android.com"; 66 private static final String REPLY_TO = "reply-to@android.com"; 67 private static final String SUBJECT = "This is the subject"; 68 private static final String BODY = "This is the body. This is also the body."; 69 private static final String MESSAGE_ID = "Test-Message-ID"; 70 private static final String MESSAGE_ID_2 = "Test-Message-ID-Second"; 71 72 EmailProvider mProvider; 73 Context mProviderContext; 74 Context mContext; 75 76 public LegacyConversionsTests() { 77 super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY); 78 } 79 80 @Override 81 public void setUp() throws Exception { 82 super.setUp(); 83 mProviderContext = getMockContext(); 84 mContext = getContext(); 85 } 86 87 @Override 88 public void tearDown() throws Exception { 89 super.tearDown(); 90 } 91 92 /** 93 * TODO: basic Legacy -> Provider Message conversions 94 * TODO: basic Legacy -> Provider Body conversions 95 * TODO: rainy day tests of all kinds 96 */ 97 98 /** 99 * Test basic conversion from Store message to Provider message 100 * 101 * TODO: Not a complete test of all fields, and some fields need special tests (e.g. flags) 102 * TODO: There are many special cases in the tested function, that need to be 103 * tested here as well. 104 */ 105 public void testUpdateMessageFields() throws MessagingException { 106 MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC, 107 REPLY_TO, SENDER, SUBJECT, null); 108 EmailContent.Message localMessage = new EmailContent.Message(); 109 110 boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1); 111 assertTrue(result); 112 checkProviderMessage("testUpdateMessageFields", message, localMessage); 113 } 114 115 /** 116 * Test basic conversion from Store message to Provider message, when the provider message 117 * does not have a proper message-id. 118 */ 119 public void testUpdateMessageFieldsNoMessageId() throws MessagingException { 120 MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC, 121 REPLY_TO, SENDER, SUBJECT, null); 122 EmailContent.Message localMessage = new EmailContent.Message(); 123 124 // If the source message-id is null, the target should be left as-is 125 localMessage.mMessageId = MESSAGE_ID_2; 126 message.removeHeader("Message-ID"); 127 128 boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1); 129 assertTrue(result); 130 assertEquals(MESSAGE_ID_2, localMessage.mMessageId); 131 } 132 133 /** 134 * Build a lightweight Store message with simple field population 135 */ 136 private MimeMessage buildTestMessage(String to, String cc, String bcc, String replyTo, 137 String sender, String subject, String content) throws MessagingException { 138 MimeMessage message = new MimeMessage(); 139 140 if (to != null) { 141 Address[] addresses = Address.parse(to); 142 message.setRecipients(RecipientType.TO, addresses); 143 } 144 if (cc != null) { 145 Address[] addresses = Address.parse(cc); 146 message.setRecipients(RecipientType.CC, addresses); 147 } 148 if (bcc != null) { 149 Address[] addresses = Address.parse(bcc); 150 message.setRecipients(RecipientType.BCC, addresses); 151 } 152 if (replyTo != null) { 153 Address[] addresses = Address.parse(replyTo); 154 message.setReplyTo(addresses); 155 } 156 if (sender != null) { 157 Address[] addresses = Address.parse(sender); 158 message.setFrom(Address.parse(sender)[0]); 159 } 160 if (subject != null) { 161 message.setSubject(subject); 162 } 163 if (content != null) { 164 TextBody body = new TextBody(content); 165 message.setBody(body); 166 } 167 168 message.setUid(UID); 169 message.setSentDate(new Date()); 170 message.setInternalDate(new Date()); 171 message.setMessageId(MESSAGE_ID); 172 return message; 173 } 174 175 /** 176 * Basic test of body parts conversion from Store message to Provider message. 177 * This tests that a null body part simply results in null text, and does not crash 178 * or return "null". 179 * 180 * TODO very incomplete, there are many permutations to be explored 181 */ 182 public void testUpdateBodyFieldsNullText() throws MessagingException { 183 EmailContent.Body localBody = new EmailContent.Body(); 184 EmailContent.Message localMessage = new EmailContent.Message(); 185 ArrayList<Part> viewables = new ArrayList<Part>(); 186 Part emptyTextPart = new MimeBodyPart(null, "text/plain"); 187 viewables.add(emptyTextPart); 188 189 // a "null" body part of type text/plain should result in a null mTextContent 190 boolean result = LegacyConversions.updateBodyFields(localBody, localMessage, viewables); 191 assertTrue(result); 192 assertNull(localBody.mTextContent); 193 } 194 195 /** 196 * Sunny day test of adding attachments from an IMAP message. 197 */ 198 public void testAddAttachments() throws MessagingException, IOException { 199 // Prepare a local message to add the attachments to 200 final long accountId = 1; 201 final long mailboxId = 1; 202 final EmailContent.Message localMessage = ProviderTestUtils.setupMessage( 203 "local-message", accountId, mailboxId, false, true, mProviderContext); 204 205 // Prepare a legacy message with attachments 206 final Message legacyMessage = prepareLegacyMessageWithAttachments(2); 207 208 // Now, convert from legacy to provider and see what happens 209 ArrayList<Part> viewables = new ArrayList<Part>(); 210 ArrayList<Part> attachments = new ArrayList<Part>(); 211 MimeUtility.collectParts(legacyMessage, viewables, attachments); 212 LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments); 213 214 // Read back all attachments for message and check field values 215 Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId); 216 Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION, 217 null, null, null); 218 try { 219 assertEquals(2, c.getCount()); 220 while (c.moveToNext()) { 221 Attachment attachment = Attachment.getContent(c, Attachment.class); 222 if ("101".equals(attachment.mLocation)) { 223 checkAttachment("attachment1Part", attachments.get(0), attachment); 224 } else if ("102".equals(attachment.mLocation)) { 225 checkAttachment("attachment2Part", attachments.get(1), attachment); 226 } else { 227 fail("Unexpected attachment with location " + attachment.mLocation); 228 } 229 } 230 } finally { 231 c.close(); 232 } 233 } 234 235 /** 236 * Test that attachments aren't re-added in the DB. This supports the "partial download" 237 * nature of POP messages. 238 */ 239 public void testAddDuplicateAttachments() throws MessagingException, IOException { 240 // Prepare a local message to add the attachments to 241 final long accountId = 1; 242 final long mailboxId = 1; 243 final EmailContent.Message localMessage = ProviderTestUtils.setupMessage( 244 "local-message", accountId, mailboxId, false, true, mProviderContext); 245 246 // Prepare a legacy message with attachments 247 Message legacyMessage = prepareLegacyMessageWithAttachments(2); 248 249 // Now, convert from legacy to provider and see what happens 250 ArrayList<Part> viewables = new ArrayList<Part>(); 251 ArrayList<Part> attachments = new ArrayList<Part>(); 252 MimeUtility.collectParts(legacyMessage, viewables, attachments); 253 LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments); 254 255 // Confirm two attachment objects created 256 Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId); 257 assertEquals(2, EmailContent.count(mProviderContext, uri, null, null)); 258 259 // Now add the attachments again and confirm there are still only two 260 LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments); 261 assertEquals(2, EmailContent.count(mProviderContext, uri, null, null)); 262 263 // Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6 264 legacyMessage = prepareLegacyMessageWithAttachments(4); 265 viewables = new ArrayList<Part>(); 266 attachments = new ArrayList<Part>(); 267 MimeUtility.collectParts(legacyMessage, viewables, attachments); 268 LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments); 269 assertEquals(4, EmailContent.count(mProviderContext, uri, null, null)); 270 } 271 272 /** 273 * Prepare a legacy message with 1+ attachments 274 */ 275 private static Message prepareLegacyMessageWithAttachments(int numAttachments) 276 throws MessagingException { 277 // First, build one or more attachment parts 278 MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed"); 279 for (int i = 1; i <= numAttachments; ++i) { 280 BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null); 281 282 // name=attachmentN size=N00 location=10N 283 attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, 284 "image/jpg;\n name=\"attachment" + i + "\""); 285 attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64"); 286 attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, 287 "attachment;\n filename=\"attachment2\";\n size=" + i + "00"); 288 attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i); 289 290 mpBuilder.addBodyPart(attachmentPart); 291 } 292 293 // Now build a message with them 294 final Message legacyMessage = new MessageBuilder() 295 .setBody(new MultipartBuilder("multipart/mixed") 296 .addBodyPart(MessageTestUtils.bodyPart("text/html", null)) 297 .addBodyPart(mpBuilder.buildBodyPart()) 298 .build()) 299 .build(); 300 301 return legacyMessage; 302 } 303 304 /** 305 * Test the stringInequal helper 306 */ 307 public void testStringInequal() { 308 // Pairs that are "equal" 309 assertFalse(LegacyConversions.stringNotEqual(null, null)); 310 assertFalse(LegacyConversions.stringNotEqual(null, "")); 311 assertFalse(LegacyConversions.stringNotEqual("", null)); 312 assertFalse(LegacyConversions.stringNotEqual("", "")); 313 assertFalse(LegacyConversions.stringNotEqual("string-equal", "string-equal")); 314 // Pairs that are "inequal" 315 assertTrue(LegacyConversions.stringNotEqual(null, "string-inequal")); 316 assertTrue(LegacyConversions.stringNotEqual("", "string-inequal")); 317 assertTrue(LegacyConversions.stringNotEqual("string-inequal", null)); 318 assertTrue(LegacyConversions.stringNotEqual("string-inequal", "")); 319 assertTrue(LegacyConversions.stringNotEqual("string-inequal-a", "string-inequal-b")); 320 } 321 322 /** 323 * Compare attachment that was converted from Part (expected) to Provider Attachment (actual) 324 * 325 * TODO content URI should only be set if we also saved a file 326 * TODO other data encodings 327 */ 328 private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual) 329 throws MessagingException { 330 String contentType = MimeUtility.unfoldAndDecode(expected.getContentType()); 331 String expectedName = MimeUtility.getHeaderParameter(contentType, "name"); 332 assertEquals(tag, expectedName, actual.mFileName); 333 assertEquals(tag, expected.getMimeType(), actual.mMimeType); 334 String disposition = expected.getDisposition(); 335 String sizeString = MimeUtility.getHeaderParameter(disposition, "size"); 336 long expectedSize = Long.parseLong(sizeString); 337 assertEquals(tag, expectedSize, actual.mSize); 338 assertEquals(tag, expected.getContentId(), actual.mContentId); 339 assertNull(tag, actual.mContentUri); 340 assertTrue(tag, 0 != actual.mMessageKey); 341 String expectedPartId = 342 expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA)[0]; 343 assertEquals(tag, expectedPartId, actual.mLocation); 344 assertEquals(tag, "B", actual.mEncoding); 345 } 346 347 /** 348 * TODO: Sunny day test of adding attachments from a POP message. 349 */ 350 351 /** 352 * Sunny day tests of converting an original message to a legacy message 353 */ 354 public void testMakeLegacyMessage() throws MessagingException { 355 // Set up and store a message in the provider 356 long account1Id = 1; 357 long mailbox1Id = 1; 358 359 // Test message 1: No body 360 EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy", 361 account1Id, mailbox1Id, false, true, mProviderContext); 362 Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1); 363 checkLegacyMessage("no body", localMessage1, getMessage1); 364 365 // Test message 2: Simple body 366 EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy", 367 account1Id, mailbox1Id, true, false, mProviderContext); 368 localMessage2.mTextReply = null; 369 localMessage2.mHtmlReply = null; 370 localMessage2.mIntroText = null; 371 localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK; 372 localMessage2.save(mProviderContext); 373 Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2); 374 checkLegacyMessage("simple body", localMessage2, getMessage2); 375 376 // Test message 3: Body + replied-to text 377 EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy", 378 account1Id, mailbox1Id, true, false, mProviderContext); 379 localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK; 380 localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY; 381 localMessage3.save(mProviderContext); 382 Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3); 383 checkLegacyMessage("reply-to", localMessage3, getMessage3); 384 385 // Test message 4: Body + forwarded text 386 EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy", 387 account1Id, mailbox1Id, true, false, mProviderContext); 388 localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK; 389 localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD; 390 localMessage4.save(mProviderContext); 391 Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4); 392 checkLegacyMessage("forwarding", localMessage4, getMessage4); 393 } 394 395 /** 396 * Check equality of a pair of converted messages 397 */ 398 private void checkProviderMessage(String tag, Message expect, EmailContent.Message actual) 399 throws MessagingException { 400 assertEquals(tag, expect.getUid(), actual.mServerId); 401 assertEquals(tag, expect.getSubject(), actual.mSubject); 402 assertEquals(tag, Address.pack(expect.getFrom()), actual.mFrom); 403 assertEquals(tag, expect.getSentDate().getTime(), actual.mTimeStamp); 404 assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.TO)), actual.mTo); 405 assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.CC)), actual.mCc); 406 assertEquals(tag, ((MimeMessage)expect).getMessageId(), actual.mMessageId); 407 assertEquals(tag, expect.isSet(Flag.SEEN), actual.mFlagRead); 408 assertEquals(tag, expect.isSet(Flag.FLAGGED), actual.mFlagFavorite); 409 } 410 411 /** 412 * Check equality of a pair of converted messages 413 */ 414 private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual) 415 throws MessagingException { 416 assertEquals(tag, expect.mServerId, actual.getUid()); 417 assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime()); 418 assertEquals(tag, expect.mSubject, actual.getSubject()); 419 assertEquals(tag, expect.mFrom, Address.pack(actual.getFrom())); 420 assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime()); 421 assertEquals(tag, expect.mTo, Address.pack(actual.getRecipients(RecipientType.TO))); 422 assertEquals(tag, expect.mCc, Address.pack(actual.getRecipients(RecipientType.CC))); 423 assertEquals(tag, expect.mBcc, Address.pack(actual.getRecipients(RecipientType.BCC))); 424 assertEquals(tag, expect.mReplyTo, Address.pack(actual.getReplyTo())); 425 assertEquals(tag, expect.mMessageId, ((MimeMessage)actual).getMessageId()); 426 // check flags 427 assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN)); 428 assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED)); 429 430 // Check the body of the message 431 ArrayList<Part> viewables = new ArrayList<Part>(); 432 ArrayList<Part> attachments = new ArrayList<Part>(); 433 MimeUtility.collectParts(actual, viewables, attachments); 434 String get1Text = null; 435 String get1Html = null; 436 String get1TextReply = null; 437 String get1HtmlReply = null; 438 String get1TextIntro = null; 439 for (Part viewable : viewables) { 440 String text = MimeUtility.getTextFromPart(viewable); 441 boolean isHtml = viewable.getMimeType().equalsIgnoreCase("text/html"); 442 String[] headers = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART); 443 if (headers != null) { 444 String header = headers[0]; 445 boolean isReply = LegacyConversions.BODY_QUOTED_PART_REPLY.equalsIgnoreCase(header); 446 boolean isFwd = LegacyConversions.BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(header); 447 boolean isIntro = LegacyConversions.BODY_QUOTED_PART_INTRO.equalsIgnoreCase(header); 448 if (isReply || isFwd) { 449 if (isHtml) { 450 get1HtmlReply = text; 451 } else { 452 get1TextReply = text; 453 } 454 } else if (isIntro) { 455 get1TextIntro = text; 456 } 457 // Check flags 458 int replyTypeFlags = expect.mFlags & EmailContent.Message.FLAG_TYPE_MASK; 459 if (isReply) { 460 assertEquals(tag, EmailContent.Message.FLAG_TYPE_REPLY, replyTypeFlags); 461 } 462 if (isFwd) { 463 assertEquals(tag, EmailContent.Message.FLAG_TYPE_FORWARD, replyTypeFlags); 464 } 465 } else { 466 if (isHtml) { 467 get1Html = text; 468 } else { 469 get1Text = text; 470 } 471 } 472 } 473 assertEquals(tag, expect.mText, get1Text); 474 assertEquals(tag, expect.mHtml, get1Html); 475 assertEquals(tag, expect.mTextReply, get1TextReply); 476 assertEquals(tag, expect.mHtmlReply, get1HtmlReply); 477 assertEquals(tag, expect.mIntroText, get1TextIntro); 478 479 // TODO Check the attachments 480 481// cv.put("attachment_count", attachments.size()); 482 } 483} 484