LegacyConversionsTests.java revision eb7752bf695b2a93854e0bb89ddbbc2236bb9aea
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.MimeHeader;
30import com.android.email.mail.internet.MimeMessage;
31import com.android.email.mail.internet.MimeUtility;
32import com.android.email.mail.internet.TextBody;
33import com.android.email.provider.EmailContent;
34import com.android.email.provider.EmailProvider;
35import com.android.email.provider.ProviderTestUtils;
36import com.android.email.provider.EmailContent.Attachment;
37
38import android.content.ContentUris;
39import android.content.Context;
40import android.database.Cursor;
41import android.net.Uri;
42import android.test.ProviderTestCase2;
43
44import java.io.IOException;
45import java.util.ArrayList;
46import java.util.Date;
47
48/**
49 * Tests of the Legacy Conversions code (used by MessagingController).
50 *
51 * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
52 * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
53 * a true "unit test".
54 *
55 * You can run this entire test case with:
56 *   runtest -c com.android.email.LegacyConversionsTests email
57 */
58public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
59
60    private static final String UID = "UID.12345678";
61    private static final String SENDER = "sender@android.com";
62    private static final String RECIPIENT_TO = "recipient-to@android.com";
63    private static final String RECIPIENT_CC = "recipient-cc@android.com";
64    private static final String RECIPIENT_BCC = "recipient-bcc@android.com";
65    private static final String REPLY_TO = "reply-to@android.com";
66    private static final String SUBJECT = "This is the subject";
67    private static final String BODY = "This is the body.  This is also the body.";
68    private static final String MESSAGE_ID = "Test-Message-ID";
69    private static final String MESSAGE_ID_2 = "Test-Message-ID-Second";
70
71    EmailProvider mProvider;
72    Context mProviderContext;
73    Context mContext;
74
75    public LegacyConversionsTests() {
76        super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
77    }
78
79    @Override
80    public void setUp() throws Exception {
81        super.setUp();
82        mProviderContext = getMockContext();
83        mContext = getContext();
84    }
85
86    @Override
87    public void tearDown() throws Exception {
88        super.tearDown();
89    }
90
91    /**
92     * TODO: basic Legacy -> Provider Message conversions
93     * TODO: basic Legacy -> Provider Body conversions
94     * TODO: rainy day tests of all kinds
95     */
96
97    /**
98     * Test basic conversion from Store message to Provider message
99     *
100     * TODO: Not a complete test of all fields, and some fields need special tests (e.g. flags)
101     * TODO: There are many special cases in the tested function, that need to be
102     * tested here as well.
103     */
104    public void testUpdateMessageFields() throws MessagingException {
105        MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
106                REPLY_TO, SENDER, SUBJECT, null);
107        EmailContent.Message localMessage = new EmailContent.Message();
108
109        boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
110        assertTrue(result);
111        checkProviderMessage("testUpdateMessageFields", message, localMessage);
112    }
113
114    /**
115     * Test basic conversion from Store message to Provider message, when the provider message
116     * does not have a proper message-id.
117     */
118    public void testUpdateMessageFieldsNoMessageId() throws MessagingException {
119        MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
120                REPLY_TO, SENDER, SUBJECT, null);
121        EmailContent.Message localMessage = new EmailContent.Message();
122
123        // If the source message-id is null, the target should be left as-is
124        localMessage.mMessageId = MESSAGE_ID_2;
125        message.removeHeader("Message-ID");
126
127        boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
128        assertTrue(result);
129        assertEquals(MESSAGE_ID_2, localMessage.mMessageId);
130    }
131
132    /**
133     * Build a lightweight Store message with simple field population
134     */
135    private MimeMessage buildTestMessage(String to, String cc, String bcc, String replyTo,
136            String sender, String subject, String content) throws MessagingException {
137        MimeMessage message = new MimeMessage();
138
139        if (to != null) {
140            Address[] addresses = Address.parse(to);
141            message.setRecipients(RecipientType.TO, addresses);
142        }
143        if (cc != null) {
144            Address[] addresses = Address.parse(cc);
145            message.setRecipients(RecipientType.CC, addresses);
146        }
147        if (bcc != null) {
148            Address[] addresses = Address.parse(bcc);
149            message.setRecipients(RecipientType.BCC, addresses);
150        }
151        if (replyTo != null) {
152            Address[] addresses = Address.parse(replyTo);
153            message.setReplyTo(addresses);
154        }
155        if (sender != null) {
156            Address[] addresses = Address.parse(sender);
157            message.setFrom(Address.parse(sender)[0]);
158        }
159        if (subject != null) {
160            message.setSubject(subject);
161        }
162        if (content != null) {
163            TextBody body = new TextBody(content);
164            message.setBody(body);
165        }
166
167        message.setUid(UID);
168        message.setSentDate(new Date());
169        message.setInternalDate(new Date());
170        message.setMessageId(MESSAGE_ID);
171        return message;
172    }
173
174    /**
175     * Sunny day test of adding attachments from an IMAP message.
176     */
177    public void testAddAttachments() throws MessagingException, IOException {
178        // Prepare a local message to add the attachments to
179        final long accountId = 1;
180        final long mailboxId = 1;
181        final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
182                "local-message", accountId, mailboxId, false, true, mProviderContext);
183
184        // Prepare a legacy message with attachments
185        Part attachment1Part = MessageTestUtils.bodyPart("image/gif", null);
186        attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
187                "image/gif;\n name=\"attachment1\"");
188        attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
189        attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
190                "attachment;\n filename=\"attachment1\";\n size=100");
191        attachment1Part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "101");
192
193        Part attachment2Part = MessageTestUtils.bodyPart("image/jpg", null);
194        attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
195                "image/jpg;\n name=\"attachment2\"");
196        attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
197        attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
198                "attachment;\n filename=\"attachment2\";\n size=200");
199        attachment2Part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "102");
200
201        final Message legacyMessage = new MessageBuilder()
202            .setBody(new MultipartBuilder("multipart/mixed")
203                     .addBodyPart(MessageTestUtils.bodyPart("text/html", null))
204                     .addBodyPart(new MultipartBuilder("multipart/mixed")
205                             .addBodyPart((BodyPart)attachment1Part)
206                             .addBodyPart((BodyPart)attachment2Part)
207                             .buildBodyPart())
208                     .build())
209                .build();
210
211        // Now, convert from legacy to provider and see what happens
212        ArrayList<Part> viewables = new ArrayList<Part>();
213        ArrayList<Part> attachments = new ArrayList<Part>();
214        MimeUtility.collectParts(legacyMessage, viewables, attachments);
215        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
216
217        // Read back all attachments for message and check field values
218        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
219        Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
220                null, null, null);
221        try {
222            assertEquals(2, c.getCount());
223            while (c.moveToNext()) {
224                Attachment attachment = Attachment.getContent(c, Attachment.class);
225                if ("101".equals(attachment.mLocation)) {
226                    checkAttachment("attachment1Part", attachment1Part, attachment);
227                } else if ("102".equals(attachment.mLocation)) {
228                    checkAttachment("attachment2Part", attachment2Part, attachment);
229                } else {
230                    fail("Unexpected attachment with location " + attachment.mLocation);
231                }
232            }
233        } finally {
234            c.close();
235        }
236    }
237
238    /**
239     * Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
240     *
241     * TODO content URI should only be set if we also saved a file
242     * TODO other data encodings
243     */
244    private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual)
245            throws MessagingException {
246        String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
247        String expectedName = MimeUtility.getHeaderParameter(contentType, "name");
248        assertEquals(tag, expectedName, actual.mFileName);
249        assertEquals(tag, expected.getMimeType(), actual.mMimeType);
250        String disposition = expected.getDisposition();
251        String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
252        long expectedSize = Long.parseLong(sizeString);
253        assertEquals(tag, expectedSize, actual.mSize);
254        assertEquals(tag, expected.getContentId(), actual.mContentId);
255        assertNull(tag, actual.mContentUri);
256        assertTrue(tag, 0 != actual.mMessageKey);
257        String expectedPartId =
258            expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA)[0];
259        assertEquals(tag, expectedPartId, actual.mLocation);
260        assertEquals(tag, "B", actual.mEncoding);
261    }
262
263    /**
264     * TODO: Sunny day test of adding attachments from a POP message.
265     */
266
267    /**
268     * Sunny day tests of converting an original message to a legacy message
269     */
270    public void testMakeLegacyMessage() throws MessagingException {
271        // Set up and store a message in the provider
272        long account1Id = 1;
273        long mailbox1Id = 1;
274
275        // Test message 1: No body
276        EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
277                account1Id, mailbox1Id, false, true, mProviderContext);
278        Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
279        checkLegacyMessage("no body", localMessage1, getMessage1);
280
281        // Test message 2: Simple body
282        EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
283                account1Id, mailbox1Id, true, false, mProviderContext);
284        localMessage2.mTextReply = null;
285        localMessage2.mHtmlReply = null;
286        localMessage2.mIntroText = null;
287        localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
288        localMessage2.save(mProviderContext);
289        Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
290        checkLegacyMessage("simple body", localMessage2, getMessage2);
291
292        // Test message 3: Body + replied-to text
293        EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
294                account1Id, mailbox1Id, true, false, mProviderContext);
295        localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
296        localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
297        localMessage3.save(mProviderContext);
298        Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
299        checkLegacyMessage("reply-to", localMessage3, getMessage3);
300
301        // Test message 4: Body + forwarded text
302        EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
303                account1Id, mailbox1Id, true, false, mProviderContext);
304        localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
305        localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
306        localMessage4.save(mProviderContext);
307        Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
308        checkLegacyMessage("forwarding", localMessage4, getMessage4);
309    }
310
311    /**
312     * Check equality of a pair of converted messages
313     */
314    private void checkProviderMessage(String tag, Message expect, EmailContent.Message actual)
315            throws MessagingException {
316        assertEquals(tag, expect.getUid(), actual.mServerId);
317        assertEquals(tag, expect.getSubject(), actual.mSubject);
318        assertEquals(tag, Address.pack(expect.getFrom()), actual.mFrom);
319        assertEquals(tag, expect.getSentDate().getTime(), actual.mTimeStamp);
320        assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.TO)), actual.mTo);
321        assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.CC)), actual.mCc);
322        assertEquals(tag, ((MimeMessage)expect).getMessageId(), actual.mMessageId);
323        assertEquals(tag, expect.isSet(Flag.SEEN), actual.mFlagRead);
324        assertEquals(tag, expect.isSet(Flag.FLAGGED), actual.mFlagFavorite);
325    }
326
327        /**
328     * Check equality of a pair of converted messages
329     */
330    private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
331            throws MessagingException {
332        assertEquals(tag, expect.mServerId, actual.getUid());
333        assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime());
334        assertEquals(tag, expect.mSubject, actual.getSubject());
335        assertEquals(tag, expect.mFrom, Address.pack(actual.getFrom()));
336        assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
337        assertEquals(tag, expect.mTo, Address.pack(actual.getRecipients(RecipientType.TO)));
338        assertEquals(tag, expect.mCc, Address.pack(actual.getRecipients(RecipientType.CC)));
339        assertEquals(tag, expect.mBcc, Address.pack(actual.getRecipients(RecipientType.BCC)));
340        assertEquals(tag, expect.mReplyTo, Address.pack(actual.getReplyTo()));
341        assertEquals(tag, expect.mMessageId, ((MimeMessage)actual).getMessageId());
342        // check flags
343        assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
344        assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
345
346        // Check the body of the message
347        ArrayList<Part> viewables = new ArrayList<Part>();
348        ArrayList<Part> attachments = new ArrayList<Part>();
349        MimeUtility.collectParts(actual, viewables, attachments);
350        String get1Text = null;
351        String get1Html = null;
352        String get1TextReply = null;
353        String get1HtmlReply = null;
354        String get1TextIntro = null;
355        for (Part viewable : viewables) {
356            String text = MimeUtility.getTextFromPart(viewable);
357            boolean isHtml = viewable.getMimeType().equalsIgnoreCase("text/html");
358            String[] headers = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
359            if (headers != null) {
360                String header = headers[0];
361                boolean isReply = LegacyConversions.BODY_QUOTED_PART_REPLY.equalsIgnoreCase(header);
362                boolean isFwd = LegacyConversions.BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(header);
363                boolean isIntro = LegacyConversions.BODY_QUOTED_PART_INTRO.equalsIgnoreCase(header);
364                if (isReply || isFwd) {
365                    if (isHtml) {
366                        get1HtmlReply = text;
367                    } else {
368                        get1TextReply = text;
369                    }
370                } else if (isIntro) {
371                    get1TextIntro = text;
372                }
373                // Check flags
374                int replyTypeFlags = expect.mFlags & EmailContent.Message.FLAG_TYPE_MASK;
375                if (isReply) {
376                    assertEquals(tag, EmailContent.Message.FLAG_TYPE_REPLY, replyTypeFlags);
377                }
378                if (isFwd) {
379                    assertEquals(tag, EmailContent.Message.FLAG_TYPE_FORWARD, replyTypeFlags);
380                }
381            } else {
382                if (isHtml) {
383                    get1Html = text;
384                } else {
385                    get1Text = text;
386                }
387            }
388        }
389        assertEquals(tag, expect.mText, get1Text);
390        assertEquals(tag, expect.mHtml, get1Html);
391        assertEquals(tag, expect.mTextReply, get1TextReply);
392        assertEquals(tag, expect.mHtmlReply, get1HtmlReply);
393        assertEquals(tag, expect.mIntroText, get1TextIntro);
394
395        // TODO Check the attachments
396
397//      cv.put("attachment_count", attachments.size());
398    }
399}
400