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 android.content.Context;
20import android.net.Uri;
21import android.test.ProviderTestCase2;
22
23import com.android.email.provider.ContentCache;
24import com.android.email.provider.EmailProvider;
25import com.android.email.provider.ProviderTestUtils;
26import com.android.emailcommon.provider.Account;
27import com.android.emailcommon.provider.EmailContent;
28import com.android.emailcommon.provider.EmailContent.Body;
29import com.android.emailcommon.provider.EmailContent.Message;
30import com.android.emailcommon.provider.HostAuth;
31import com.android.emailcommon.provider.Mailbox;
32
33import java.util.Locale;
34import java.util.concurrent.ExecutionException;
35
36/**
37 * Tests of the Controller class that depend on the underlying provider.
38 *
39 * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
40 * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
41 * a true "unit test".
42 *
43 * You can run this entire test case with:
44 *   runtest -c com.android.email.ControllerProviderOpsTests email
45 */
46public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider> {
47
48    private Context mProviderContext;
49    private Context mContext;
50    private TestController mTestController;
51
52
53    public ControllerProviderOpsTests() {
54        super(EmailProvider.class, EmailContent.AUTHORITY);
55    }
56
57    @Override
58    public void setUp() throws Exception {
59        super.setUp();
60        mProviderContext = getMockContext();
61        mContext = getContext();
62        mTestController = new TestController(mProviderContext, mContext);
63        // Invalidate all caches, since we reset the database for each test
64        ContentCache.invalidateAllCaches();
65    }
66
67    @Override
68    public void tearDown() throws Exception {
69        super.tearDown();
70        mTestController.cleanupForTest();
71    }
72
73    /**
74     * Lightweight subclass of the Controller class allows injection of mock context
75     */
76    public static class TestController extends Controller {
77
78        protected TestController(Context providerContext, Context systemContext) {
79            super(systemContext);
80            setProviderContext(providerContext);
81        }
82    }
83
84    /**
85     * These are strings that should not change per locale.
86     */
87    public void testGetMailboxServerName() {
88        assertEquals("", Controller.getMailboxServerName(mContext, -1));
89
90        assertEquals("Inbox", Controller.getMailboxServerName(mContext, Mailbox.TYPE_INBOX));
91        assertEquals("Outbox", Controller.getMailboxServerName(mContext, Mailbox.TYPE_OUTBOX));
92        assertEquals("Trash", Controller.getMailboxServerName(mContext, Mailbox.TYPE_TRASH));
93        assertEquals("Sent", Controller.getMailboxServerName(mContext, Mailbox.TYPE_SENT));
94        assertEquals("Junk", Controller.getMailboxServerName(mContext, Mailbox.TYPE_JUNK));
95
96        // Now try again with translation
97        Locale savedLocale = Locale.getDefault();
98        Locale.setDefault(Locale.FRANCE);
99        assertEquals("Inbox", Controller.getMailboxServerName(mContext, Mailbox.TYPE_INBOX));
100        assertEquals("Outbox", Controller.getMailboxServerName(mContext, Mailbox.TYPE_OUTBOX));
101        assertEquals("Trash", Controller.getMailboxServerName(mContext, Mailbox.TYPE_TRASH));
102        assertEquals("Sent", Controller.getMailboxServerName(mContext, Mailbox.TYPE_SENT));
103        assertEquals("Junk", Controller.getMailboxServerName(mContext, Mailbox.TYPE_JUNK));
104        Locale.setDefault(savedLocale);
105    }
106
107    /**
108     * Test of Controller.createMailbox().
109     * Sunny day test only - creates a mailbox that does not exist.
110     * Does not test duplication, bad accountID, or any other bad input.
111     */
112    public void testCreateMailbox() {
113        // safety check that system mailboxes don't exist ...
114        assertEquals(Mailbox.NO_MAILBOX,
115                Mailbox.findMailboxOfType(mProviderContext, 1L, Mailbox.TYPE_DRAFTS));
116        assertEquals(Mailbox.NO_MAILBOX,
117                Mailbox.findMailboxOfType(mProviderContext, 1L, Mailbox.TYPE_SENT));
118
119        long testMailboxId;
120        Mailbox testMailbox;
121
122        // Test creating "drafts" mailbox
123        mTestController.createMailbox(1L, Mailbox.TYPE_DRAFTS);
124        testMailboxId = Mailbox.findMailboxOfType(mProviderContext, 1L, Mailbox.TYPE_DRAFTS);
125        assertTrue(testMailboxId != Mailbox.NO_MAILBOX);
126        testMailbox = Mailbox.restoreMailboxWithId(mProviderContext, testMailboxId);
127        assertNotNull(testMailbox);
128        assertEquals(8, testMailbox.mFlags);        // Flags should be "holds mail"
129        assertEquals(-1L, testMailbox.mParentKey);  // Parent is off the top-level
130
131        // Test creating "sent" mailbox; same as drafts
132        mTestController.createMailbox(1L, Mailbox.TYPE_SENT);
133        testMailboxId = Mailbox.findMailboxOfType(mProviderContext, 1L, Mailbox.TYPE_SENT);
134        assertTrue(testMailboxId != Mailbox.NO_MAILBOX);
135        testMailbox = Mailbox.restoreMailboxWithId(mProviderContext, testMailboxId);
136        assertNotNull(testMailbox);
137        assertEquals(8, testMailbox.mFlags);        // Flags should be "holds mail"
138        assertEquals(-1L, testMailbox.mParentKey);  // Parent is off the top-level
139    }
140
141    /**
142     * Test of Controller.findOrCreateMailboxOfType().
143     * Checks:
144     * - finds correctly the ID of existing mailbox
145     * - creates non-existing mailbox
146     * - creates only once a new mailbox
147     * - when accountId or mailboxType are -1, returns NO_MAILBOX
148     */
149    public void testFindOrCreateMailboxOfType() {
150        Account account = ProviderTestUtils.setupAccount("mailboxid", true, mProviderContext);
151        long accountId = account.mId;
152        Mailbox box = ProviderTestUtils.setupMailbox("box", accountId, false, mProviderContext);
153        final int boxType = Mailbox.TYPE_TRASH;
154        box.mType = boxType;
155        box.save(mProviderContext);
156        long boxId = box.mId;
157
158        long testBoxId = mTestController.findOrCreateMailboxOfType(accountId, boxType);
159
160        // check it found the right mailbox id
161        assertEquals(boxId, testBoxId);
162
163        long boxId2 = mTestController.findOrCreateMailboxOfType(accountId, Mailbox.TYPE_DRAFTS);
164        assertTrue("mailbox created", boxId2 != Mailbox.NO_MAILBOX);
165        assertTrue("with different id", testBoxId != boxId2);
166
167        // check it doesn't create twice when existing
168        long boxId3 = mTestController.findOrCreateMailboxOfType(accountId, Mailbox.TYPE_DRAFTS);
169        assertEquals("don't create if exists", boxId3, boxId2);
170
171        // check invalid aruments
172        assertEquals(Mailbox.NO_MAILBOX,
173                mTestController.findOrCreateMailboxOfType(-1, Mailbox.TYPE_DRAFTS));
174        assertEquals(Mailbox.NO_MAILBOX, mTestController.findOrCreateMailboxOfType(accountId, -1));
175    }
176
177    /**
178     * Test the "move message" function.
179     */
180    public void testMoveMessage() throws InterruptedException, ExecutionException {
181        Account account1 = ProviderTestUtils.setupAccount("message-move", true, mProviderContext);
182        long account1Id = account1.mId;
183        Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mProviderContext);
184        long box1Id = box1.mId;
185        Mailbox box2 = ProviderTestUtils.setupMailbox("box2", account1Id, true, mProviderContext);
186        long box2Id = box2.mId;
187        Mailbox boxDest = ProviderTestUtils.setupMailbox("d", account1Id, true, mProviderContext);
188        long boxDestId = boxDest.mId;
189
190        Message message1 = ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false,
191                true, mProviderContext);
192        Message message2 = ProviderTestUtils.setupMessage("message2", account1Id, box2Id, false,
193                true, mProviderContext);
194        long message1Id = message1.mId;
195        long message2Id = message2.mId;
196
197        // Because moveMessage runs asynchronously, call get() to force it to complete
198        mTestController.moveMessages(new long[] { message1Id, message2Id }, boxDestId).get();
199
200        // now read back a fresh copy and confirm it's in the trash
201        assertEquals(boxDestId, EmailContent.Message.restoreMessageWithId(mProviderContext,
202                message1Id).mMailboxKey);
203        assertEquals(boxDestId, EmailContent.Message.restoreMessageWithId(mProviderContext,
204                message2Id).mMailboxKey);
205    }
206
207    /**
208     * Test the "delete message" function.  Sunny day:
209     *    - message/mailbox/account all exist
210     *    - trash mailbox exists
211     */
212    public void testDeleteMessage() {
213        Account account1 = ProviderTestUtils.setupAccount("message-delete", true, mProviderContext);
214        long account1Id = account1.mId;
215        Mailbox box = ProviderTestUtils.setupMailbox("box1", account1Id, true, mProviderContext);
216        long boxId = box.mId;
217
218        Mailbox trashBox = ProviderTestUtils.setupMailbox("box2", account1Id, false,
219                mProviderContext);
220        trashBox.mType = Mailbox.TYPE_TRASH;
221        trashBox.save(mProviderContext);
222        long trashBoxId = trashBox.mId;
223
224        Mailbox draftBox = ProviderTestUtils.setupMailbox("box3", account1Id, false,
225                mProviderContext);
226        draftBox.mType = Mailbox.TYPE_DRAFTS;
227        draftBox.save(mProviderContext);
228        long draftBoxId = draftBox.mId;
229
230        {
231            // Case 1: Message in a regular mailbox, account known.
232            Message message = ProviderTestUtils.setupMessage("message1", account1Id, boxId, false,
233                    true, mProviderContext);
234            long messageId = message.mId;
235
236            mTestController.deleteMessageSync(messageId);
237
238            // now read back a fresh copy and confirm it's in the trash
239            Message restored = EmailContent.Message.restoreMessageWithId(mProviderContext,
240                    messageId);
241            assertEquals(trashBoxId, restored.mMailboxKey);
242        }
243
244        {
245            // Case 2: Already in trash
246            Message message = ProviderTestUtils.setupMessage("message3", account1Id, trashBoxId,
247                    false, true, mProviderContext);
248            long messageId = message.mId;
249
250            mTestController.deleteMessageSync(messageId);
251
252            // Message should be deleted.
253            assertNull(EmailContent.Message.restoreMessageWithId(mProviderContext, messageId));
254        }
255
256        {
257            // Case 3: Draft
258            Message message = ProviderTestUtils.setupMessage("message3", account1Id, draftBoxId,
259                    false, true, mProviderContext);
260            long messageId = message.mId;
261
262            mTestController.deleteMessageSync(messageId);
263
264            // Message should be deleted.
265            assertNull(EmailContent.Message.restoreMessageWithId(mProviderContext, messageId));
266        }
267    }
268
269    /**
270     * Test deleting message when there is no trash mailbox
271     */
272    public void testDeleteMessageNoTrash() {
273        Account account1 =
274                ProviderTestUtils.setupAccount("message-delete-notrash", true, mProviderContext);
275        long account1Id = account1.mId;
276        Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mProviderContext);
277        long box1Id = box1.mId;
278
279        Message message1 =
280                ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false, true,
281                        mProviderContext);
282        long message1Id = message1.mId;
283
284        mTestController.deleteMessageSync(message1Id);
285
286        // now read back a fresh copy and confirm it's in the trash
287        Message message1get =
288                EmailContent.Message.restoreMessageWithId(mProviderContext, message1Id);
289
290        // check the new mailbox and see if it looks right
291        assertFalse(-1 == message1get.mMailboxKey);
292        assertFalse(box1Id == message1get.mMailboxKey);
293        Mailbox mailbox2get = Mailbox.restoreMailboxWithId(mProviderContext,
294                message1get.mMailboxKey);
295        assertEquals(Mailbox.TYPE_TRASH, mailbox2get.mType);
296    }
297
298    /**
299     * Test read/unread flag
300     */
301    public void testReadUnread() throws InterruptedException, ExecutionException {
302        Account account1 = ProviderTestUtils.setupAccount("read-unread", false, mProviderContext);
303        account1.mHostAuthRecv
304                = ProviderTestUtils.setupHostAuth("read-unread", 0, false, mProviderContext);
305        account1.save(mProviderContext);
306        long account1Id = account1.mId;
307        long box1Id = 2;
308
309        Message message1 =
310                ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false, true,
311                        mProviderContext);
312        long message1Id = message1.mId;
313
314        // test setting to "read"
315        mTestController.setMessageRead(message1Id, true).get();
316        Message message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
317        assertTrue(message1get.mFlagRead);
318
319        // test setting to "unread"
320        mTestController.setMessageRead(message1Id, false).get();
321        message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
322        assertFalse(message1get.mFlagRead);
323
324        // test setting to "read"
325        mTestController.setMessageRead(message1Id, true).get();
326        message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
327        assertTrue(message1get.mFlagRead);
328    }
329
330    /**
331     * Test favorites flag
332     */
333    public void testFavorites() throws InterruptedException, ExecutionException {
334        Account account1 = ProviderTestUtils.setupAccount("favorites", false, mProviderContext);
335        account1.mHostAuthRecv
336                = ProviderTestUtils.setupHostAuth("favorites", 0, false, mProviderContext);
337        account1.save(mProviderContext);
338        long account1Id = account1.mId;
339        long box1Id = 2;
340
341        Message message1 =
342                ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false, true,
343                        mProviderContext);
344        long message1Id = message1.mId;
345
346        // test setting to "favorite"
347        mTestController.setMessageFavorite(message1Id, true).get();
348        Message message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
349        assertTrue(message1get.mFlagFavorite);
350
351        // test setting to "not favorite"
352        mTestController.setMessageFavorite(message1Id, false).get();
353        message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
354        assertFalse(message1get.mFlagFavorite);
355
356        // test setting to "favorite"
357        mTestController.setMessageFavorite(message1Id, true).get();
358        message1get = Message.restoreMessageWithId(mProviderContext, message1Id);
359        assertTrue(message1get.mFlagFavorite);
360    }
361
362    public void testGetAndDeleteAttachmentMailbox() {
363        Mailbox box = mTestController.getAttachmentMailbox();
364        assertNotNull(box);
365        Mailbox anotherBox = mTestController.getAttachmentMailbox();
366        assertNotNull(anotherBox);
367        // We should always get back the same Mailbox row
368        assertEquals(box.mId, anotherBox.mId);
369        // Add two messages to this mailbox
370        ProviderTestUtils.setupMessage("message1", 0, box.mId, false, true,
371                mProviderContext);
372        ProviderTestUtils.setupMessage("message2", 0, box.mId, false, true,
373                mProviderContext);
374        // Make sure we can find them where they are expected
375        assertEquals(2, EmailContent.count(mProviderContext, Message.CONTENT_URI,
376                Message.MAILBOX_KEY + "=?", new String[] {Long.toString(box.mId)}));
377        // Delete them
378        mTestController.deleteAttachmentMessages();
379        // Make sure they're gone
380        assertEquals(0, EmailContent.count(mProviderContext, Message.CONTENT_URI,
381                Message.MAILBOX_KEY + "=?", new String[] {Long.toString(box.mId)}));
382    }
383
384    /**
385     * Test wiping an account's synced data.  Everything should go, but account & empty inbox.
386     * Also ensures that the remaining account and the remaining inbox have cleared their
387     * server sync keys, to force refresh eventually.
388     */
389    public void testWipeSyncedData() {
390        Account account1 = ProviderTestUtils.setupAccount("wipe-synced-1", false, mProviderContext);
391        account1.mSyncKey = "account-1-sync-key";
392        account1.save(mProviderContext);
393        long account1Id = account1.mId;
394        Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, false, mProviderContext);
395        box1.mType = Mailbox.TYPE_INBOX;
396        box1.mSyncKey = "box-1-sync-key";
397        box1.save(mProviderContext);
398        long box1Id = box1.mId;
399        Mailbox box2 = ProviderTestUtils.setupMailbox("box2", account1Id, true, mProviderContext);
400        long box2Id = box2.mId;
401        // An EAS account mailbox
402        Mailbox eas = ProviderTestUtils.setupMailbox("eas", account1Id, false, mProviderContext);
403        eas.mType = Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
404        eas.save(mProviderContext);
405
406        Account account2 = ProviderTestUtils.setupAccount("wipe-synced-2", false, mProviderContext);
407        account2.mSyncKey = "account-2-sync-key";
408        account2.save(mProviderContext);
409        long account2Id = account2.mId;
410        Mailbox box3 = ProviderTestUtils.setupMailbox("box3", account2Id, false, mProviderContext);
411        box3.mSyncKey = "box-3-sync-key";
412        box3.mType = Mailbox.TYPE_INBOX;
413        box3.save(mProviderContext);
414        long box3Id = box3.mId;
415        Mailbox box4 = ProviderTestUtils.setupMailbox("box4", account2Id, true, mProviderContext);
416        long box4Id = box4.mId;
417
418        // Now populate the 4 non-account boxes with messages
419        Message message = ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false,
420                true, mProviderContext);
421        long message1Id = message.mId;
422        message = ProviderTestUtils.setupMessage("message2", account1Id, box2Id, false,
423                true, mProviderContext);
424        long message2Id = message.mId;
425        message = ProviderTestUtils.setupMessage("message3", account2Id, box3Id, false,
426                true, mProviderContext);
427        long message3Id = message.mId;
428        message = ProviderTestUtils.setupMessage("message4", account2Id, box4Id, false,
429                true, mProviderContext);
430        long message4Id = message.mId;
431
432        // Now wipe account 1's data
433        mTestController.deleteSyncedDataSync(account1Id);
434
435        // Confirm:  Mailboxes gone (except account box), all messages gone, account survives
436        assertNull(Mailbox.restoreMailboxWithId(mProviderContext, box1Id));
437        assertNull(Mailbox.restoreMailboxWithId(mProviderContext, box2Id));
438        assertNotNull(Mailbox.restoreMailboxWithId(mProviderContext, eas.mId));
439        assertNull(Message.restoreMessageWithId(mProviderContext, message1Id));
440        assertNull(Message.restoreMessageWithId(mProviderContext, message2Id));
441        account1 = Account.restoreAccountWithId(mProviderContext, account1Id);
442        assertNotNull(account1);
443        assertNull(account1.mSyncKey);
444
445        // Confirm:  Other account survived
446        assertNotNull(Mailbox.restoreMailboxWithId(mProviderContext, box3Id));
447        assertNotNull(Mailbox.restoreMailboxWithId(mProviderContext, box4Id));
448        assertNotNull(Message.restoreMessageWithId(mProviderContext, message3Id));
449        assertNotNull(Message.restoreMessageWithId(mProviderContext, message4Id));
450        assertNotNull(Account.restoreAccountWithId(mProviderContext, account2Id));
451    }
452
453    public void testLoadMessageFromUri() throws Exception {
454        // Create a simple message
455        Message msg = new Message();
456        String text = "This is some text";
457        msg.mText = text;
458        String sender = "sender@host.com";
459        msg.mFrom = sender;
460        // Save this away
461        msg.save(mProviderContext);
462
463        Uri fileUri = ProviderTestUtils.createTempEmlFile(mProviderContext, msg,
464                mContext.getFilesDir());
465
466        // Load the message via Controller and a Uri
467        Message loadedMsg = mTestController.loadMessageFromUri(fileUri);
468
469        // Check server id, mailbox key, account key, and from
470        assertNotNull(loadedMsg);
471        assertTrue(loadedMsg.mServerId.startsWith(Controller.ATTACHMENT_MESSAGE_UID_PREFIX));
472        Mailbox box = mTestController.getAttachmentMailbox();
473        assertNotNull(box);
474        assertEquals(box.mId, loadedMsg.mMailboxKey);
475        assertEquals(0, loadedMsg.mAccountKey);
476        assertEquals(loadedMsg.mFrom, sender);
477        // Check body text
478        String loadedMsgText = Body.restoreBodyTextWithMessageId(mProviderContext, loadedMsg.mId);
479        assertEquals(text, loadedMsgText);
480    }
481
482    /**
483     * Create a simple HostAuth with protocol
484     */
485    private HostAuth setupSimpleHostAuth(String protocol) {
486        HostAuth hostAuth = new HostAuth();
487        hostAuth.mProtocol = protocol;
488        return hostAuth;
489    }
490
491    public void testIsMessagingController() {
492        Account account1 = ProviderTestUtils.setupAccount("account1", false,
493                mProviderContext);
494        account1.mHostAuthRecv = setupSimpleHostAuth("eas");
495        account1.save(mProviderContext);
496        assertFalse(mTestController.isMessagingController(account1));
497        Account account2 = ProviderTestUtils.setupAccount("account2", false,
498                mProviderContext);
499        account2.mHostAuthRecv = setupSimpleHostAuth("imap");
500        account2.save(mProviderContext);
501        assertTrue(mTestController.isMessagingController(account2));
502        Account account3 = ProviderTestUtils.setupAccount("account3", false,
503                mProviderContext);
504        account3.mHostAuthRecv = setupSimpleHostAuth("pop3");
505        account3.save(mProviderContext);
506        assertTrue(mTestController.isMessagingController(account3));
507        Account account4 = ProviderTestUtils.setupAccount("account4", false,
508                mProviderContext);
509        account4.mHostAuthRecv = setupSimpleHostAuth("smtp");
510        account4.save(mProviderContext);
511        assertFalse(mTestController.isMessagingController(account4));
512        // There should be values for all of these accounts in the legacy map
513        assertNotNull(mTestController.mLegacyControllerMap.get(account1.mId));
514        assertNotNull(mTestController.mLegacyControllerMap.get(account2.mId));
515        assertNotNull(mTestController.mLegacyControllerMap.get(account3.mId));
516        assertNotNull(mTestController.mLegacyControllerMap.get(account4.mId));
517        // The map should have the expected values
518        assertFalse(mTestController.mLegacyControllerMap.get(account1.mId));
519        assertTrue(mTestController.mLegacyControllerMap.get(account2.mId));
520        assertTrue(mTestController.mLegacyControllerMap.get(account3.mId));
521        assertFalse(mTestController.mLegacyControllerMap.get(account4.mId));
522        // This second pass should pull values from the cache
523        assertFalse(mTestController.isMessagingController(account1));
524        assertTrue(mTestController.isMessagingController(account2));
525        assertTrue(mTestController.isMessagingController(account3));
526        assertFalse(mTestController.isMessagingController(account4));
527    }
528
529    /**
530     * TODO: releasing associated data (e.g. attachments, embedded images)
531     */
532}
533