Home | History | Annotate | Download | only in email
      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 
     17 package com.android.email;
     18 
     19 import android.content.ContentUris;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.net.Uri;
     23 import android.test.ProviderTestCase2;
     24 import android.test.suitebuilder.annotation.Suppress;
     25 
     26 import com.android.email.provider.EmailProvider;
     27 import com.android.email.provider.ProviderTestUtils;
     28 import com.android.emailcommon.internet.MimeHeader;
     29 import com.android.emailcommon.internet.MimeUtility;
     30 import com.android.emailcommon.mail.Address;
     31 import com.android.emailcommon.mail.BodyPart;
     32 import com.android.emailcommon.mail.Flag;
     33 import com.android.emailcommon.mail.Message;
     34 import com.android.emailcommon.mail.Message.RecipientType;
     35 import com.android.emailcommon.mail.MessageTestUtils;
     36 import com.android.emailcommon.mail.MessageTestUtils.MessageBuilder;
     37 import com.android.emailcommon.mail.MessageTestUtils.MultipartBuilder;
     38 import com.android.emailcommon.mail.MessagingException;
     39 import com.android.emailcommon.mail.Part;
     40 import com.android.emailcommon.provider.EmailContent;
     41 import com.android.emailcommon.provider.EmailContent.Attachment;
     42 
     43 import java.io.IOException;
     44 import java.util.ArrayList;
     45 
     46 /**
     47  * Tests of the Legacy Conversions code (used by MessagingController).
     48  *
     49  * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
     50  * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
     51  * a true "unit test".
     52  *
     53  * You can run this entire test case with:
     54  *   runtest -c com.android.email.LegacyConversionsTests email
     55  */
     56 @Suppress
     57 public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
     58 
     59     Context mProviderContext;
     60     Context mContext;
     61 
     62     public LegacyConversionsTests() {
     63         super(EmailProvider.class, EmailContent.AUTHORITY);
     64     }
     65 
     66     @Override
     67     public void setUp() throws Exception {
     68         super.setUp();
     69         mProviderContext = getMockContext();
     70         mContext = getContext();
     71     }
     72 
     73     /**
     74      * TODO: basic Legacy -> Provider Message conversions
     75      * TODO: basic Legacy -> Provider Body conversions
     76      * TODO: rainy day tests of all kinds
     77      */
     78 
     79     /**
     80      * Sunny day test of adding attachments from an IMAP/POP message.
     81      */
     82     public void brokentestAddAttachments() throws MessagingException, IOException {
     83         // Prepare a local message to add the attachments to
     84         final long accountId = 1;
     85         final long mailboxId = 1;
     86 
     87         // test 1: legacy message using content-type:name style for name
     88         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
     89                 "local-message", accountId, mailboxId, false, true, mProviderContext);
     90         final Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
     91         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
     92 
     93         // test 2: legacy message using content-disposition:filename style for name
     94         final EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage(
     95                 "local-message", accountId, mailboxId, false, true, mProviderContext);
     96         final Message legacyMessage2 = prepareLegacyMessageWithAttachments(2, true);
     97         convertAndCheckcheckAddedAttachments(localMessage2, legacyMessage2);
     98     }
     99 
    100     /**
    101      * Helper for testAddAttachments
    102      */
    103     private void convertAndCheckcheckAddedAttachments(final EmailContent.Message localMessage,
    104             final Message legacyMessage) throws MessagingException, IOException {
    105         // Now, convert from legacy to provider and see what happens
    106         ArrayList<Part> viewables = new ArrayList<Part>();
    107         ArrayList<Part> attachments = new ArrayList<Part>();
    108         MimeUtility.collectParts(legacyMessage, viewables, attachments);
    109         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
    110 
    111         // Read back all attachments for message and check field values
    112         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
    113         Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
    114                 null, null, null);
    115         try {
    116             assertEquals(2, c.getCount());
    117             while (c.moveToNext()) {
    118                 Attachment attachment =
    119                         Attachment.getContent(mProviderContext, c, Attachment.class);
    120                 if ("100".equals(attachment.mLocation)) {
    121                     checkAttachment("attachment1Part", attachments.get(0), attachment,
    122                             localMessage.mAccountKey);
    123                 } else if ("101".equals(attachment.mLocation)) {
    124                     checkAttachment("attachment2Part", attachments.get(1), attachment,
    125                             localMessage.mAccountKey);
    126                 } else {
    127                     fail("Unexpected attachment with location " + attachment.mLocation);
    128                 }
    129             }
    130         } finally {
    131             c.close();
    132         }
    133     }
    134 
    135     /**
    136      * Test that only "attachment" or "inline" attachments are captured and added.
    137      * @throws MessagingException
    138      * @throws IOException
    139      */
    140     public void brokentestAttachmentDispositions() throws MessagingException, IOException {
    141         // Prepare a local message to add the attachments to
    142         final long accountId = 1;
    143         final long mailboxId = 1;
    144 
    145         // Prepare the three attachments we want to test
    146         BodyPart[] sourceAttachments = new BodyPart[3];
    147         BodyPart attachmentPart;
    148 
    149         // 1. Standard attachment
    150         attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
    151         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg");
    152         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
    153         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
    154                 "attachment;\n filename=\"file-1\";\n size=100");
    155         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "100");
    156         sourceAttachments[0] = attachmentPart;
    157 
    158         // 2. Inline attachment
    159         attachmentPart = MessageTestUtils.bodyPart("image/gif", null);
    160         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/gif");
    161         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
    162         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
    163                 "inline;\n filename=\"file-2\";\n size=200");
    164         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "101");
    165         sourceAttachments[1] = attachmentPart;
    166 
    167         // 3. Neither (use VCALENDAR)
    168         attachmentPart = MessageTestUtils.bodyPart("text/calendar", null);
    169         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
    170                 "text/calendar; charset=UTF-8; method=REQUEST");
    171         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "7bit");
    172         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "102");
    173         sourceAttachments[2] = attachmentPart;
    174 
    175         // Prepare local message (destination) and legacy message w/attachments (source)
    176         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
    177                 "local-message", accountId, mailboxId, false, true, mProviderContext);
    178         final Message legacyMessage = prepareLegacyMessageWithAttachments(sourceAttachments);
    179         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
    180 
    181         // Run the conversion and check for the converted attachments - this test asserts
    182         // that there are two attachments numbered 100 & 101 (so will fail if it finds 102)
    183         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
    184     }
    185 
    186     /**
    187      * Test that attachments aren't re-added in the DB.  This supports the "partial download"
    188      * nature of POP messages.
    189      */
    190     public void brokentestAddDuplicateAttachments() throws MessagingException, IOException {
    191         // Prepare a local message to add the attachments to
    192         final long accountId = 1;
    193         final long mailboxId = 1;
    194         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
    195                 "local-message", accountId, mailboxId, false, true, mProviderContext);
    196 
    197         // Prepare a legacy message with attachments
    198         Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
    199 
    200         // Now, convert from legacy to provider and see what happens
    201         ArrayList<Part> viewables = new ArrayList<Part>();
    202         ArrayList<Part> attachments = new ArrayList<Part>();
    203         MimeUtility.collectParts(legacyMessage, viewables, attachments);
    204         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
    205 
    206         // Confirm two attachment objects created
    207         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
    208         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
    209 
    210         // Now add the attachments again and confirm there are still only two
    211         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
    212         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
    213 
    214         // Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6
    215         legacyMessage = prepareLegacyMessageWithAttachments(4, false);
    216         viewables = new ArrayList<Part>();
    217         attachments = new ArrayList<Part>();
    218         MimeUtility.collectParts(legacyMessage, viewables, attachments);
    219         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
    220         assertEquals(4, EmailContent.count(mProviderContext, uri, null, null));
    221     }
    222 
    223     /**
    224      * Prepare a legacy message with 1+ attachments
    225      * @param numAttachments how many attachments to add
    226      * @param filenameInDisposition False: attachment names are sent as content-type:name.  True:
    227      *          attachment names are sent as content-disposition:filename.
    228      */
    229     private Message prepareLegacyMessageWithAttachments(int numAttachments,
    230             boolean filenameInDisposition) throws MessagingException {
    231         BodyPart[] attachmentParts = new BodyPart[numAttachments];
    232         for (int i = 0; i < numAttachments; ++i) {
    233             // construct parameter parts for content-type:name or content-disposition:filename.
    234             String name = "";
    235             String filename = "";
    236             String quotedName = "\"test-attachment-" + i + "\"";
    237             if (filenameInDisposition) {
    238                 filename = ";\n filename=" + quotedName;
    239             } else {
    240                 name = ";\n name=" + quotedName;
    241             }
    242 
    243             // generate an attachment that came from a server
    244             BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
    245 
    246             // name=attachmentN size=N00 location=10N
    247             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg" + name);
    248             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
    249             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
    250                     "attachment" + filename +  ";\n size=" + (i+1) + "00");
    251             attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
    252 
    253             attachmentParts[i] = attachmentPart;
    254         }
    255 
    256         return prepareLegacyMessageWithAttachments(attachmentParts);
    257     }
    258 
    259     /**
    260      * Prepare a legacy message with 1+ attachments
    261      * @param attachments array containing one or more attachments
    262      */
    263     private Message prepareLegacyMessageWithAttachments(BodyPart[] attachments)
    264             throws MessagingException {
    265         // Build the multipart that holds the attachments
    266         MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed");
    267         for (int i = 0; i < attachments.length; ++i) {
    268             mpBuilder.addBodyPart(attachments[i]);
    269         }
    270 
    271         // Now build a message with them
    272         final Message legacyMessage = new MessageBuilder()
    273             .setBody(new MultipartBuilder("multipart/mixed")
    274                      .addBodyPart(MessageTestUtils.bodyPart("text/html", null))
    275                      .addBodyPart(mpBuilder.buildBodyPart())
    276                      .build())
    277                 .build();
    278 
    279         return legacyMessage;
    280     }
    281 
    282     /**
    283      * Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
    284      *
    285      * TODO content URI should only be set if we also saved a file
    286      * TODO other data encodings
    287      */
    288     private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual,
    289             long accountKey) throws MessagingException {
    290         String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
    291         String contentTypeName = MimeUtility.getHeaderParameter(contentType, "name");
    292         assertEquals(tag, expected.getMimeType(), actual.mMimeType);
    293         String disposition = expected.getDisposition();
    294         String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
    295         String dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
    296         long expectedSize = (sizeString != null) ? Long.parseLong(sizeString) : 0;
    297         assertEquals(tag, expectedSize, actual.mSize);
    298         assertEquals(tag, expected.getContentId(), actual.mContentId);
    299 
    300         // filename is either content-type:name or content-disposition:filename
    301         String expectedName = (contentTypeName != null) ? contentTypeName : dispositionFilename;
    302         assertEquals(tag, expectedName, actual.mFileName);
    303 
    304         // content URI should be null
    305         assertNull(tag, actual.getContentUri());
    306 
    307         assertTrue(tag, 0 != actual.mMessageKey);
    308 
    309         // location is either both null or both matching
    310         String expectedPartId = null;
    311         String[] storeData = expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
    312         if (storeData != null && storeData.length > 0) {
    313             expectedPartId = storeData[0];
    314         }
    315         assertEquals(tag, expectedPartId, actual.mLocation);
    316         assertEquals(tag, "B", actual.mEncoding);
    317         assertEquals(tag, accountKey, actual.mAccountKey);
    318     }
    319 
    320     /**
    321      * TODO: Sunny day test of adding attachments from a POP message.
    322      */
    323 
    324     /**
    325      * Sunny day tests of converting an original message to a legacy message
    326      */
    327     public void brokentestMakeLegacyMessage() throws MessagingException {
    328         // Set up and store a message in the provider
    329         long account1Id = 1;
    330         long mailbox1Id = 1;
    331 
    332         // Test message 1: No body
    333         EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
    334                 account1Id, mailbox1Id, false, true, mProviderContext);
    335         Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
    336         checkLegacyMessage("no body", localMessage1, getMessage1);
    337 
    338         // Test message 2: Simple body
    339         EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
    340                 account1Id, mailbox1Id, true, false, mProviderContext);
    341         localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
    342         localMessage2.save(mProviderContext);
    343         Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
    344         checkLegacyMessage("simple body", localMessage2, getMessage2);
    345 
    346         // Test message 3: Body + replied-to text
    347         EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
    348                 account1Id, mailbox1Id, true, false, mProviderContext);
    349         localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
    350         localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
    351         localMessage3.save(mProviderContext);
    352         Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
    353         checkLegacyMessage("reply-to", localMessage3, getMessage3);
    354 
    355         // Test message 4: Body + forwarded text
    356         EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
    357                 account1Id, mailbox1Id, true, false, mProviderContext);
    358         localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
    359         localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
    360         localMessage4.save(mProviderContext);
    361         Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
    362         checkLegacyMessage("forwarding", localMessage4, getMessage4);
    363     }
    364 
    365     /**
    366      * Check equality of a pair of converted messages
    367      */
    368     private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
    369             throws MessagingException {
    370         assertEquals(tag, expect.mServerId, actual.getUid());
    371         assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime());
    372         assertEquals(tag, expect.mSubject, actual.getSubject());
    373         assertEquals(tag, expect.mFrom, Address.toHeader(actual.getFrom()));
    374         assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
    375         assertEquals(tag, expect.mTo, Address.toHeader(actual.getRecipients(RecipientType.TO)));
    376         assertEquals(tag, expect.mCc, Address.toHeader(actual.getRecipients(RecipientType.CC)));
    377         assertEquals(tag, expect.mBcc, Address.toHeader(actual.getRecipients(RecipientType.BCC)));
    378         assertEquals(tag, expect.mReplyTo, Address.toHeader(actual.getReplyTo()));
    379         assertEquals(tag, expect.mMessageId, actual.getMessageId());
    380         // check flags
    381         assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
    382         assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
    383 
    384         // Check the body of the message
    385         ArrayList<Part> viewables = new ArrayList<Part>();
    386         ArrayList<Part> attachments = new ArrayList<Part>();
    387         MimeUtility.collectParts(actual, viewables, attachments);
    388         String get1Text = null;
    389         String get1Html = null;
    390         for (Part viewable : viewables) {
    391             String text = MimeUtility.getTextFromPart(viewable);
    392             if (viewable.getMimeType().equalsIgnoreCase("text/html")) {
    393                 get1Html = text;
    394             } else {
    395                 get1Text = text;
    396             }
    397         }
    398         assertEquals(tag, expect.mText, get1Text);
    399         assertEquals(tag, expect.mHtml, get1Html);
    400 
    401         // TODO Check the attachments
    402 
    403 //      cv.put("attachment_count", attachments.size());
    404     }
    405 }
    406