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