LegacyConversionsTests.java revision 75a873be8420e50f0aeb5a77716358ee0ca66b01
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.Body;
21import com.android.email.mail.BodyPart;
22import com.android.email.mail.Flag;
23import com.android.email.mail.Folder;
24import com.android.email.mail.Folder.OpenMode;
25import com.android.email.mail.Message;
26import com.android.email.mail.Message.RecipientType;
27import com.android.email.mail.MessageTestUtils;
28import com.android.email.mail.MessageTestUtils.MessageBuilder;
29import com.android.email.mail.MessageTestUtils.MultipartBuilder;
30import com.android.email.mail.MessagingException;
31import com.android.email.mail.Part;
32import com.android.email.mail.internet.MimeBodyPart;
33import com.android.email.mail.internet.MimeHeader;
34import com.android.email.mail.internet.MimeMessage;
35import com.android.email.mail.internet.MimeUtility;
36import com.android.email.mail.internet.TextBody;
37import com.android.email.mail.store.LocalStore;
38import com.android.email.mail.store.LocalStoreUnitTests;
39import com.android.email.provider.EmailContent;
40import com.android.email.provider.EmailContent.Attachment;
41import com.android.email.provider.EmailContent.Mailbox;
42import com.android.email.provider.EmailProvider;
43import com.android.email.provider.ProviderTestUtils;
44
45import android.content.ContentUris;
46import android.content.Context;
47import android.database.Cursor;
48import android.net.Uri;
49import android.test.ProviderTestCase2;
50
51import java.io.IOException;
52import java.util.ArrayList;
53import java.util.Date;
54
55/**
56 * Tests of the Legacy Conversions code (used by MessagingController).
57 *
58 * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
59 * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
60 * a true "unit test".
61 *
62 * You can run this entire test case with:
63 *   runtest -c com.android.email.LegacyConversionsTests email
64 */
65public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
66
67    private static final String UID = "UID.12345678";
68    private static final String SENDER = "sender@android.com";
69    private static final String RECIPIENT_TO = "recipient-to@android.com";
70    private static final String RECIPIENT_CC = "recipient-cc@android.com";
71    private static final String RECIPIENT_BCC = "recipient-bcc@android.com";
72    private static final String REPLY_TO = "reply-to@android.com";
73    private static final String SUBJECT = "This is the subject";
74    private static final String MESSAGE_ID = "Test-Message-ID";
75    private static final String MESSAGE_ID_2 = "Test-Message-ID-Second";
76
77    EmailProvider mProvider;
78    Context mProviderContext;
79    Context mContext;
80    Account mLegacyAccount = null;
81    Preferences mPreferences = null;
82
83    public LegacyConversionsTests() {
84        super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
85    }
86
87    @Override
88    public void setUp() throws Exception {
89        super.setUp();
90        mProviderContext = getMockContext();
91        mContext = getContext();
92    }
93
94    @Override
95    public void tearDown() throws Exception {
96        super.tearDown();
97        if (mLegacyAccount != null) {
98            mLegacyAccount.delete(mPreferences);
99        }
100    }
101
102    /**
103     * TODO: basic Legacy -> Provider Message conversions
104     * TODO: basic Legacy -> Provider Body conversions
105     * TODO: rainy day tests of all kinds
106     */
107
108    /**
109     * Test basic conversion from Store message to Provider message
110     *
111     * TODO: Not a complete test of all fields, and some fields need special tests (e.g. flags)
112     * TODO: There are many special cases in the tested function, that need to be
113     * tested here as well.
114     */
115    public void testUpdateMessageFields() throws MessagingException {
116        MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
117                REPLY_TO, SENDER, SUBJECT, null);
118        EmailContent.Message localMessage = new EmailContent.Message();
119
120        boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
121        assertTrue(result);
122        checkProviderMessage("testUpdateMessageFields", message, localMessage);
123    }
124
125    /**
126     * Test basic conversion from Store message to Provider message, when the provider message
127     * does not have a proper message-id.
128     */
129    public void testUpdateMessageFieldsNoMessageId() throws MessagingException {
130        MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
131                REPLY_TO, SENDER, SUBJECT, null);
132        EmailContent.Message localMessage = new EmailContent.Message();
133
134        // If the source message-id is null, the target should be left as-is
135        localMessage.mMessageId = MESSAGE_ID_2;
136        message.removeHeader("Message-ID");
137
138        boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
139        assertTrue(result);
140        assertEquals(MESSAGE_ID_2, localMessage.mMessageId);
141    }
142
143    /**
144     * Build a lightweight Store message with simple field population
145     */
146    private MimeMessage buildTestMessage(String to, String cc, String bcc, String replyTo,
147            String sender, String subject, String content) throws MessagingException {
148        MimeMessage message = new MimeMessage();
149
150        if (to != null) {
151            Address[] addresses = Address.parse(to);
152            message.setRecipients(RecipientType.TO, addresses);
153        }
154        if (cc != null) {
155            Address[] addresses = Address.parse(cc);
156            message.setRecipients(RecipientType.CC, addresses);
157        }
158        if (bcc != null) {
159            Address[] addresses = Address.parse(bcc);
160            message.setRecipients(RecipientType.BCC, addresses);
161        }
162        if (replyTo != null) {
163            Address[] addresses = Address.parse(replyTo);
164            message.setReplyTo(addresses);
165        }
166        if (sender != null) {
167            Address[] addresses = Address.parse(sender);
168            message.setFrom(addresses[0]);
169        }
170        if (subject != null) {
171            message.setSubject(subject);
172        }
173        if (content != null) {
174            TextBody body = new TextBody(content);
175            message.setBody(body);
176        }
177
178        message.setUid(UID);
179        message.setSentDate(new Date());
180        message.setInternalDate(new Date());
181        message.setMessageId(MESSAGE_ID);
182        return message;
183    }
184
185    /**
186     * Basic test of body parts conversion from Store message to Provider message.
187     * This tests that a null body part simply results in null text, and does not crash
188     * or return "null".
189     *
190     * TODO very incomplete, there are many permutations to be explored
191     */
192    public void testUpdateBodyFieldsNullText() throws MessagingException {
193        EmailContent.Body localBody = new EmailContent.Body();
194        EmailContent.Message localMessage = new EmailContent.Message();
195        ArrayList<Part> viewables = new ArrayList<Part>();
196        Part emptyTextPart = new MimeBodyPart(null, "text/plain");
197        viewables.add(emptyTextPart);
198
199        // a "null" body part of type text/plain should result in a null mTextContent
200        boolean result = LegacyConversions.updateBodyFields(localBody, localMessage, viewables);
201        assertTrue(result);
202        assertNull(localBody.mTextContent);
203    }
204
205    /**
206     * Sunny day test of adding attachments from an IMAP/POP message.
207     */
208    public void testAddAttachments() throws MessagingException, IOException {
209        // Prepare a local message to add the attachments to
210        final long accountId = 1;
211        final long mailboxId = 1;
212
213        // test 1: legacy message using content-type:name style for name
214        final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
215                "local-message", accountId, mailboxId, false, true, mProviderContext);
216        final Message legacyMessage = prepareLegacyMessageWithAttachments(2, false, false);
217        convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
218
219        // test 2: legacy message using content-disposition:filename style for name
220        final EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage(
221                "local-message", accountId, mailboxId, false, true, mProviderContext);
222        final Message legacyMessage2 = prepareLegacyMessageWithAttachments(2, false, true);
223        convertAndCheckcheckAddedAttachments(localMessage2, legacyMessage2);
224    }
225
226    /**
227     * Helper for testAddAttachments
228     */
229    private void convertAndCheckcheckAddedAttachments(final EmailContent.Message localMessage,
230            final Message legacyMessage) throws MessagingException, IOException {
231        // Now, convert from legacy to provider and see what happens
232        ArrayList<Part> viewables = new ArrayList<Part>();
233        ArrayList<Part> attachments = new ArrayList<Part>();
234        MimeUtility.collectParts(legacyMessage, viewables, attachments);
235        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
236
237        // Read back all attachments for message and check field values
238        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
239        Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
240                null, null, null);
241        try {
242            assertEquals(2, c.getCount());
243            while (c.moveToNext()) {
244                Attachment attachment = Attachment.getContent(c, Attachment.class);
245                if ("101".equals(attachment.mLocation)) {
246                    checkAttachment("attachment1Part", attachments.get(0), attachment,
247                            localMessage.mAccountKey);
248                } else if ("102".equals(attachment.mLocation)) {
249                    checkAttachment("attachment2Part", attachments.get(1), attachment,
250                            localMessage.mAccountKey);
251                } else {
252                    fail("Unexpected attachment with location " + attachment.mLocation);
253                }
254            }
255        } finally {
256            c.close();
257        }
258    }
259
260    /**
261     * Test that attachments aren't re-added in the DB.  This supports the "partial download"
262     * nature of POP messages.
263     */
264    public void testAddDuplicateAttachments() throws MessagingException, IOException {
265        // Prepare a local message to add the attachments to
266        final long accountId = 1;
267        final long mailboxId = 1;
268        final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
269                "local-message", accountId, mailboxId, false, true, mProviderContext);
270
271        // Prepare a legacy message with attachments
272        Message legacyMessage = prepareLegacyMessageWithAttachments(2, false, false);
273
274        // Now, convert from legacy to provider and see what happens
275        ArrayList<Part> viewables = new ArrayList<Part>();
276        ArrayList<Part> attachments = new ArrayList<Part>();
277        MimeUtility.collectParts(legacyMessage, viewables, attachments);
278        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
279
280        // Confirm two attachment objects created
281        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
282        assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
283
284        // Now add the attachments again and confirm there are still only two
285        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
286        assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
287
288        // Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6
289        legacyMessage = prepareLegacyMessageWithAttachments(4, false, false);
290        viewables = new ArrayList<Part>();
291        attachments = new ArrayList<Part>();
292        MimeUtility.collectParts(legacyMessage, viewables, attachments);
293        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
294        assertEquals(4, EmailContent.count(mProviderContext, uri, null, null));
295    }
296
297    /**
298     * Sunny day test of adding attachments in "local account upgrade" mode
299     */
300    public void testLocalUpgradeAttachments() throws MessagingException, IOException {
301        // Prepare a local message to add the attachments to
302        final long accountId = 1;
303        final long mailboxId = 1;
304        final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
305                "local-upgrade", accountId, mailboxId, false, true, mProviderContext);
306
307        // Prepare a legacy message with attachments
308        final Message legacyMessage = prepareLegacyMessageWithAttachments(2, true, false);
309
310        // Now, convert from legacy to provider and see what happens
311        ArrayList<Part> viewables = new ArrayList<Part>();
312        ArrayList<Part> attachments = new ArrayList<Part>();
313        MimeUtility.collectParts(legacyMessage, viewables, attachments);
314        LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, true);
315
316        // Read back all attachments for message and check field values
317        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
318        Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
319                null, null, null);
320        try {
321            assertEquals(2, c.getCount());
322            while (c.moveToNext()) {
323                Attachment attachment = Attachment.getContent(c, Attachment.class);
324                // This attachment should look as if created by modern (provider) MessageCompose.
325                // 1. find the original that it was created from
326                Part fromPart = null;
327                for (Part from : attachments) {
328                    String contentType = MimeUtility.unfoldAndDecode(from.getContentType());
329                    String name = MimeUtility.getHeaderParameter(contentType, "name");
330                    if (name.equals(attachment.mFileName)) {
331                        fromPart = from;
332                        break;
333                    }
334                }
335                assertTrue(fromPart != null);
336                // 2. Check values
337                checkAttachment(attachment.mFileName, fromPart, attachment, accountId);
338            }
339        } finally {
340            c.close();
341        }
342    }
343
344    /**
345     * Prepare a legacy message with 1+ attachments
346     * @param numAttachments how many attachments to add
347     * @param localData if true, attachments are "local" data.  false = "remote" (from server)
348     * @param filenameInDisposition False: attachment names are sent as content-type:name.  True:
349     *          attachment names are sent as content-disposition:filename.
350     */
351    private Message prepareLegacyMessageWithAttachments(int numAttachments, boolean localData,
352            boolean filenameInDisposition) throws MessagingException {
353        // First, build one or more attachment parts
354        MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed");
355        for (int i = 1; i <= numAttachments; ++i) {
356            // construct parameter parts for content-type:name or content-disposition:filename.
357            String name = "";
358            String filename = "";
359            String quotedName = "\"test-attachment-" + i + "\"";
360            if (filenameInDisposition) {
361                filename = ";\n filename=" + quotedName;
362            } else {
363                name = ";\n name=" + quotedName;
364            }
365            if (localData) {
366                // generate an attachment that was generated by legacy code (e.g. donut)
367                // for test of upgrading accounts in place
368                // This creator models the code in legacy MessageCompose
369                Uri uri = Uri.parse("content://test/attachment/" + i);
370                MimeBodyPart bp = new MimeBodyPart(
371                        new LocalStore.LocalAttachmentBody(uri, mProviderContext));
372                bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg" + name);
373                bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
374                bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment" + filename);
375                mpBuilder.addBodyPart(bp);
376            } else {
377                // generate an attachment that came from a server
378                BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
379
380                // name=attachmentN size=N00 location=10N
381                attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg" + name);
382                attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
383                attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
384                        "attachment" + filename +  ";\n size=" + i + "00");
385                attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
386
387                mpBuilder.addBodyPart(attachmentPart);
388            }
389        }
390
391        // Now build a message with them
392        final Message legacyMessage = new MessageBuilder()
393            .setBody(new MultipartBuilder("multipart/mixed")
394                     .addBodyPart(MessageTestUtils.bodyPart("text/html", null))
395                     .addBodyPart(mpBuilder.buildBodyPart())
396                     .build())
397                .build();
398
399        return legacyMessage;
400    }
401
402    /**
403     * Test the stringInequal helper
404     */
405    public void testStringInequal() {
406        // Pairs that are "equal"
407        assertFalse(LegacyConversions.stringNotEqual(null, null));
408        assertFalse(LegacyConversions.stringNotEqual(null, ""));
409        assertFalse(LegacyConversions.stringNotEqual("", null));
410        assertFalse(LegacyConversions.stringNotEqual("", ""));
411        assertFalse(LegacyConversions.stringNotEqual("string-equal", "string-equal"));
412        // Pairs that are "inequal"
413        assertTrue(LegacyConversions.stringNotEqual(null, "string-inequal"));
414        assertTrue(LegacyConversions.stringNotEqual("", "string-inequal"));
415        assertTrue(LegacyConversions.stringNotEqual("string-inequal", null));
416        assertTrue(LegacyConversions.stringNotEqual("string-inequal", ""));
417        assertTrue(LegacyConversions.stringNotEqual("string-inequal-a", "string-inequal-b"));
418    }
419
420    /**
421     * Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
422     *
423     * TODO content URI should only be set if we also saved a file
424     * TODO other data encodings
425     */
426    private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual,
427            long accountKey) throws MessagingException {
428        String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
429        String contentTypeName = MimeUtility.getHeaderParameter(contentType, "name");
430        assertEquals(tag, expected.getMimeType(), actual.mMimeType);
431        String disposition = expected.getDisposition();
432        String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
433        String dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
434        long expectedSize = (sizeString != null) ? Long.parseLong(sizeString) : 0;
435        assertEquals(tag, expectedSize, actual.mSize);
436        assertEquals(tag, expected.getContentId(), actual.mContentId);
437
438        // filename is either content-type:name or content-disposition:filename
439        String expectedName = (contentTypeName != null) ? contentTypeName : dispositionFilename;
440        assertEquals(tag, expectedName, actual.mFileName);
441
442        // content URI either both null or both matching
443        String expectedUriString = null;
444        Body body = expected.getBody();
445        if (body instanceof LocalStore.LocalAttachmentBody) {
446            LocalStore.LocalAttachmentBody localBody = (LocalStore.LocalAttachmentBody) body;
447            Uri contentUri = localBody.getContentUri();
448            if (contentUri != null) {
449                expectedUriString = contentUri.toString();
450            }
451        }
452        assertEquals(tag, expectedUriString, actual.mContentUri);
453
454        assertTrue(tag, 0 != actual.mMessageKey);
455
456        // location is either both null or both matching
457        String expectedPartId = null;
458        String[] storeData = expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
459        if (storeData != null && storeData.length > 0) {
460            expectedPartId = storeData[0];
461        }
462        assertEquals(tag, expectedPartId, actual.mLocation);
463        assertEquals(tag, "B", actual.mEncoding);
464        assertEquals(tag, accountKey, actual.mAccountKey);
465    }
466
467    /**
468     * TODO: Sunny day test of adding attachments from a POP message.
469     */
470
471    /**
472     * Sunny day tests of converting an original message to a legacy message
473     */
474    public void testMakeLegacyMessage() throws MessagingException {
475        // Set up and store a message in the provider
476        long account1Id = 1;
477        long mailbox1Id = 1;
478
479        // Test message 1: No body
480        EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
481                account1Id, mailbox1Id, false, true, mProviderContext);
482        Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
483        checkLegacyMessage("no body", localMessage1, getMessage1);
484
485        // Test message 2: Simple body
486        EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
487                account1Id, mailbox1Id, true, false, mProviderContext);
488        localMessage2.mTextReply = null;
489        localMessage2.mHtmlReply = null;
490        localMessage2.mIntroText = null;
491        localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
492        localMessage2.save(mProviderContext);
493        Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
494        checkLegacyMessage("simple body", localMessage2, getMessage2);
495
496        // Test message 3: Body + replied-to text
497        EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
498                account1Id, mailbox1Id, true, false, mProviderContext);
499        localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
500        localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
501        localMessage3.save(mProviderContext);
502        Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
503        checkLegacyMessage("reply-to", localMessage3, getMessage3);
504
505        // Test message 4: Body + forwarded text
506        EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
507                account1Id, mailbox1Id, true, false, mProviderContext);
508        localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
509        localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
510        localMessage4.save(mProviderContext);
511        Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
512        checkLegacyMessage("forwarding", localMessage4, getMessage4);
513    }
514
515    /**
516     * Check equality of a pair of converted messages
517     */
518    private void checkProviderMessage(String tag, Message expect, EmailContent.Message actual)
519            throws MessagingException {
520        assertEquals(tag, expect.getUid(), actual.mServerId);
521        assertEquals(tag, expect.getSubject(), actual.mSubject);
522        assertEquals(tag, Address.pack(expect.getFrom()), actual.mFrom);
523        assertEquals(tag, expect.getSentDate().getTime(), actual.mTimeStamp);
524        assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.TO)), actual.mTo);
525        assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.CC)), actual.mCc);
526        assertEquals(tag, ((MimeMessage)expect).getMessageId(), actual.mMessageId);
527        assertEquals(tag, expect.isSet(Flag.SEEN), actual.mFlagRead);
528        assertEquals(tag, expect.isSet(Flag.FLAGGED), actual.mFlagFavorite);
529    }
530
531    /**
532     * Check equality of a pair of converted messages
533     */
534    private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
535            throws MessagingException {
536        assertEquals(tag, expect.mServerId, actual.getUid());
537        assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime());
538        assertEquals(tag, expect.mSubject, actual.getSubject());
539        assertEquals(tag, expect.mFrom, Address.pack(actual.getFrom()));
540        assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
541        assertEquals(tag, expect.mTo, Address.pack(actual.getRecipients(RecipientType.TO)));
542        assertEquals(tag, expect.mCc, Address.pack(actual.getRecipients(RecipientType.CC)));
543        assertEquals(tag, expect.mBcc, Address.pack(actual.getRecipients(RecipientType.BCC)));
544        assertEquals(tag, expect.mReplyTo, Address.pack(actual.getReplyTo()));
545        assertEquals(tag, expect.mMessageId, ((MimeMessage)actual).getMessageId());
546        // check flags
547        assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
548        assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
549
550        // Check the body of the message
551        ArrayList<Part> viewables = new ArrayList<Part>();
552        ArrayList<Part> attachments = new ArrayList<Part>();
553        MimeUtility.collectParts(actual, viewables, attachments);
554        String get1Text = null;
555        String get1Html = null;
556        String get1TextReply = null;
557        String get1HtmlReply = null;
558        String get1TextIntro = null;
559        for (Part viewable : viewables) {
560            String text = MimeUtility.getTextFromPart(viewable);
561            boolean isHtml = viewable.getMimeType().equalsIgnoreCase("text/html");
562            String[] headers = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
563            if (headers != null) {
564                String header = headers[0];
565                boolean isReply = LegacyConversions.BODY_QUOTED_PART_REPLY.equalsIgnoreCase(header);
566                boolean isFwd = LegacyConversions.BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(header);
567                boolean isIntro = LegacyConversions.BODY_QUOTED_PART_INTRO.equalsIgnoreCase(header);
568                if (isReply || isFwd) {
569                    if (isHtml) {
570                        get1HtmlReply = text;
571                    } else {
572                        get1TextReply = text;
573                    }
574                } else if (isIntro) {
575                    get1TextIntro = text;
576                }
577                // Check flags
578                int replyTypeFlags = expect.mFlags & EmailContent.Message.FLAG_TYPE_MASK;
579                if (isReply) {
580                    assertEquals(tag, EmailContent.Message.FLAG_TYPE_REPLY, replyTypeFlags);
581                }
582                if (isFwd) {
583                    assertEquals(tag, EmailContent.Message.FLAG_TYPE_FORWARD, replyTypeFlags);
584                }
585            } else {
586                if (isHtml) {
587                    get1Html = text;
588                } else {
589                    get1Text = text;
590                }
591            }
592        }
593        assertEquals(tag, expect.mText, get1Text);
594        assertEquals(tag, expect.mHtml, get1Html);
595        assertEquals(tag, expect.mTextReply, get1TextReply);
596        assertEquals(tag, expect.mHtmlReply, get1HtmlReply);
597        assertEquals(tag, expect.mIntroText, get1TextIntro);
598
599        // TODO Check the attachments
600
601//      cv.put("attachment_count", attachments.size());
602    }
603
604    /**
605     * Test conversion of a legacy account to a provider account
606     */
607    public void testMakeProviderAccount() throws MessagingException {
608
609        setupLegacyAccount("testMakeProviderAccount", true);
610        EmailContent.Account toAccount =
611            LegacyConversions.makeAccount(mProviderContext, mLegacyAccount);
612        checkProviderAccount("testMakeProviderAccount", mLegacyAccount, toAccount);
613    }
614
615    /**
616     * Test conversion of a provider account to a legacy account
617     */
618    public void testMakeLegacyAccount() throws MessagingException {
619        EmailContent.Account fromAccount = ProviderTestUtils.setupAccount("convert-to-legacy",
620                false, mProviderContext);
621        fromAccount.mHostAuthRecv =
622            ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mProviderContext);
623        fromAccount.mHostAuthSend =
624            ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mProviderContext);
625        fromAccount.save(mProviderContext);
626
627        Account toAccount = LegacyConversions.makeLegacyAccount(mProviderContext, fromAccount);
628        checkLegacyAccount("testMakeLegacyAccount", fromAccount, toAccount);
629    }
630
631    /**
632     * Setup a legacy account in mLegacyAccount with many fields prefilled.
633     */
634    private void setupLegacyAccount(String name, boolean saveIt) {
635        // prefs & legacy account are saved for cleanup (it's stored in the real prefs file)
636        mPreferences = Preferences.getPreferences(mProviderContext);
637        mLegacyAccount = new Account(mProviderContext);
638
639        // fill in useful fields
640        mLegacyAccount.mUuid = "test-uid-" + name;
641        mLegacyAccount.mStoreUri = "store://test/" + name;
642        mLegacyAccount.mLocalStoreUri = "local://localhost/" + name;
643        mLegacyAccount.mSenderUri = "sender://test/" + name;
644        mLegacyAccount.mDescription = "description " + name;
645        mLegacyAccount.mName = "name " + name;
646        mLegacyAccount.mEmail = "email " + name;
647        mLegacyAccount.mAutomaticCheckIntervalMinutes = 100;
648        mLegacyAccount.mLastAutomaticCheckTime = 200;
649        mLegacyAccount.mNotifyNewMail = true;
650        mLegacyAccount.mDraftsFolderName = "drafts " + name;
651        mLegacyAccount.mSentFolderName = "sent " + name;
652        mLegacyAccount.mTrashFolderName = "trash " + name;
653        mLegacyAccount.mOutboxFolderName = "outbox " + name;
654        mLegacyAccount.mAccountNumber = 300;
655        mLegacyAccount.mVibrate = true;
656        mLegacyAccount.mVibrateWhenSilent = false;
657        mLegacyAccount.mRingtoneUri = "ringtone://test/" + name;
658        mLegacyAccount.mSyncWindow = 400;
659        mLegacyAccount.mBackupFlags = 0;
660        mLegacyAccount.mDeletePolicy = Account.DELETE_POLICY_NEVER;
661        mLegacyAccount.mSecurityFlags = 500;
662        mLegacyAccount.mSignature = "signature " + name;
663
664        if (saveIt) {
665            mLegacyAccount.save(mPreferences);
666        }
667    }
668
669    /**
670     * Compare a provider account to the legacy account it was created from
671     */
672    private void checkProviderAccount(String tag, Account expect, EmailContent.Account actual)
673            throws MessagingException {
674        assertEquals(tag + " description", expect.getDescription(), actual.mDisplayName);
675        assertEquals(tag + " email", expect.getEmail(), actual.mEmailAddress);
676        assertEquals(tag + " sync key", null, actual.mSyncKey);
677        assertEquals(tag + " lookback", expect.getSyncWindow(), actual.mSyncLookback);
678        assertEquals(tag + " sync intvl", expect.getAutomaticCheckIntervalMinutes(),
679                actual.mSyncInterval);
680        // These asserts are checking mHostAuthKeyRecv & mHostAuthKeySend
681        assertEquals(tag + " store", expect.getStoreUri(), actual.getStoreUri(mProviderContext));
682        assertEquals(tag + " sender", expect.getSenderUri(), actual.getSenderUri(mProviderContext));
683        // Synthesize & check flags
684        int expectFlags = 0;
685        if (expect.mNotifyNewMail) expectFlags |= EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL;
686        if (expect.mVibrate) expectFlags |= EmailContent.Account.FLAGS_VIBRATE_ALWAYS;
687        if (expect.mVibrateWhenSilent)
688            expectFlags |= EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT;
689        expectFlags |=
690            (expect.mDeletePolicy << EmailContent.Account.FLAGS_DELETE_POLICY_SHIFT)
691                & EmailContent.Account.FLAGS_DELETE_POLICY_MASK;
692        assertEquals(tag + " flags", expectFlags, actual.mFlags);
693        assertEquals(tag + " default", false, actual.mIsDefault);
694        assertEquals(tag + " uuid", expect.getUuid(), actual.mCompatibilityUuid);
695        assertEquals(tag + " name", expect.getName(), actual.mSenderName);
696        assertEquals(tag + " ringtone", expect.getRingtone(), actual.mRingtoneUri);
697        assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
698        assertEquals(tag + " new count", 0, actual.mNewMessageCount);
699        assertEquals(tag + " security", expect.mSecurityFlags, actual.mSecurityFlags);
700        assertEquals(tag + " sec sync key", null, actual.mSecuritySyncKey);
701        assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
702    }
703
704    /**
705     * Compare a legacy account to the provider account it was created from
706     */
707    private void checkLegacyAccount(String tag, EmailContent.Account expect, Account actual)
708            throws MessagingException {
709        int expectFlags = expect.getFlags();
710
711        assertEquals(tag + " uuid", expect.mCompatibilityUuid, actual.mUuid);
712        assertEquals(tag + " store", expect.getStoreUri(mProviderContext), actual.mStoreUri);
713        assertTrue(actual.mLocalStoreUri.startsWith("local://localhost"));
714        assertEquals(tag + " sender", expect.getSenderUri(mProviderContext), actual.mSenderUri);
715        assertEquals(tag + " description", expect.getDisplayName(), actual.mDescription);
716        assertEquals(tag + " name", expect.getSenderName(), actual.mName);
717        assertEquals(tag + " email", expect.getEmailAddress(), actual.mEmail);
718        assertEquals(tag + " checkintvl", expect.getSyncInterval(),
719                actual.mAutomaticCheckIntervalMinutes);
720        assertEquals(tag + " checktime", 0, actual.mLastAutomaticCheckTime);
721        assertEquals(tag + " notify",
722                (expectFlags & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL) != 0,
723                actual.mNotifyNewMail);
724        assertEquals(tag + " drafts", null, actual.mDraftsFolderName);
725        assertEquals(tag + " sent", null, actual.mSentFolderName);
726        assertEquals(tag + " trash", null, actual.mTrashFolderName);
727        assertEquals(tag + " outbox", null, actual.mOutboxFolderName);
728        assertEquals(tag + " acct #", -1, actual.mAccountNumber);
729        assertEquals(tag + " vibrate",
730                (expectFlags & EmailContent.Account.FLAGS_VIBRATE_ALWAYS) != 0,
731                actual.mVibrate);
732        assertEquals(tag + " vibrateSilent",
733                (expectFlags & EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT) != 0,
734                actual.mVibrateWhenSilent);
735        assertEquals(tag + " ", expect.getRingtone(), actual.mRingtoneUri);
736        assertEquals(tag + " sync window", expect.getSyncLookback(), actual.mSyncWindow);
737        assertEquals(tag + " backup flags", 0, actual.mBackupFlags);
738        assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
739        assertEquals(tag + " delete policy", expect.getDeletePolicy(), actual.getDeletePolicy());
740        assertEquals(tag + " security", expect.mSecurityFlags, actual.mSecurityFlags);
741        assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
742    }
743
744    /**
745     * Test conversion of a legacy mailbox to a provider mailbox
746     */
747    public void testMakeProviderMailbox() throws MessagingException {
748        EmailContent.Account toAccount = ProviderTestUtils.setupAccount("convert-mailbox",
749                true, mProviderContext);
750        Folder fromFolder = buildTestFolder("INBOX");
751        Mailbox toMailbox = LegacyConversions.makeMailbox(mProviderContext, toAccount, fromFolder);
752
753        // Now test fields in created mailbox
754        assertEquals("INBOX", toMailbox.mDisplayName);
755        assertNull(toMailbox.mServerId);
756        assertNull(toMailbox.mParentServerId);
757        assertEquals(toAccount.mId, toMailbox.mAccountKey);
758        assertEquals(Mailbox.TYPE_INBOX, toMailbox.mType);
759        assertEquals(0, toMailbox.mDelimiter);
760        assertNull(toMailbox.mSyncKey);
761        assertEquals(0, toMailbox.mSyncLookback);
762        assertEquals(0, toMailbox.mSyncInterval);
763        assertEquals(0, toMailbox.mSyncTime);
764        assertTrue(toMailbox.mFlagVisible);
765        assertEquals(0, toMailbox.mFlags);
766        assertEquals(Email.VISIBLE_LIMIT_DEFAULT, toMailbox.mVisibleLimit);
767        assertNull(toMailbox.mSyncStatus);
768    }
769
770    /**
771     * Build a lightweight Store Folder with simple field population.  The folder is "open"
772     * and should be closed by the caller.
773     */
774    private Folder buildTestFolder(String folderName) throws MessagingException {
775        String localStoreUri =
776            "local://localhost/" + mProviderContext.getDatabasePath(LocalStoreUnitTests.DB_NAME);
777        LocalStore store = (LocalStore) LocalStore.newInstance(localStoreUri, getContext(), null);
778        LocalStore.LocalFolder folder = (LocalStore.LocalFolder) store.getFolder(folderName);
779        folder.open(OpenMode.READ_WRITE, null);     // this will create it
780
781        // set a few fields to test values
782        // folder.getName - set by getFolder()
783        folder.setUnreadMessageCount(100);
784
785        return folder;
786    }
787}
788