1/*
2 * Copyright (C) 2010 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.activity;
18
19import android.content.Context;
20import android.test.InstrumentationTestCase;
21import android.test.ProviderTestCase2;
22import android.test.suitebuilder.annotation.LargeTest;
23
24import com.android.email.Controller;
25import com.android.email.DBTestHelper;
26import com.android.email.Email;
27import com.android.email.TestUtils;
28import com.android.email.provider.ProviderTestUtils;
29import com.android.emailcommon.mail.MessagingException;
30import com.android.emailcommon.provider.Account;
31import com.android.emailcommon.provider.Mailbox;
32
33/**
34 * Test case for {@link MailboxFinder}.
35 *
36 * We need to use {@link InstrumentationTestCase} so that we can create AsyncTasks on the UI thread
37 * using {@link InstrumentationTestCase#runTestOnUiThread}.  This class also needs an isolated
38 * context, which is provided by {@link ProviderTestCase2}.  We can't derive from two classes,
39 * so we just copy the code for an isolate context to here.
40 */
41@LargeTest
42public class MailboxFinderTest extends InstrumentationTestCase {
43    private static final int TIMEOUT = 10; // in seconds
44
45    // Test target
46    private MailboxFinder mMailboxFinder;
47
48    // Isolted Context for providers.
49    private Context mProviderContext;
50
51    // Mock to track callback invocations.
52    private MockController mMockController;
53    private MockCallback mCallback;
54
55    private Context getContext() {
56        return getInstrumentation().getTargetContext();
57    }
58
59    @Override
60    protected void setUp() throws Exception {
61        super.setUp();
62
63        mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
64                getInstrumentation().getTargetContext());
65        mCallback = new MockCallback();
66        mMockController = new MockController(getContext());
67        Controller.injectMockControllerForTest(mMockController);
68        assertEquals(0, mMockController.getResultCallbacksForTest().size());
69    }
70
71    @Override
72    protected void tearDown() throws Exception {
73        super.tearDown();
74        if (mMailboxFinder != null) {
75            mMailboxFinder.cancel();
76
77            // MailboxFinder should unregister its listener when closed.
78            checkControllerResultRemoved(mMockController);
79        }
80        mMockController.cleanupForTest();
81        Controller.injectMockControllerForTest(null);
82    }
83
84    /**
85     * Make sure no {@link MailboxFinder.Callback} is left registered to the controller.
86     */
87    private static void checkControllerResultRemoved(Controller controller) {
88        for (Controller.Result callback : controller.getResultCallbacksForTest()) {
89            assertFalse(callback instanceof MailboxFinder.Callback);
90        }
91    }
92
93    /**
94     * Create an account and returns the ID.
95     */
96    private long createAccount(boolean securityHold) {
97        Account acct = ProviderTestUtils.setupAccount("acct1", false, mProviderContext);
98        if (securityHold) {
99            acct.mFlags |= Account.FLAGS_SECURITY_HOLD;
100        }
101        acct.save(mProviderContext);
102        return acct.mId;
103    }
104
105    /**
106     * Create a mailbox and return the ID.
107     */
108    private long createMailbox(long accountId, int mailboxType) {
109        Mailbox box = new Mailbox();
110        box.mServerId = box.mDisplayName = "mailbox";
111        box.mAccountKey = accountId;
112        box.mType = mailboxType;
113        box.mFlagVisible = true;
114        box.mVisibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
115        box.save(mProviderContext);
116        return box.mId;
117    }
118
119    /**
120     * Create a {@link MailboxFinder} and kick it.
121     */
122    private void createAndStartFinder(final long accountId, final int mailboxType)
123            throws Throwable {
124        runTestOnUiThread(new Runnable() {
125            @Override
126            public void run() {
127                mMailboxFinder = new MailboxFinder(mProviderContext, accountId, mailboxType,
128                        mCallback);
129                mMailboxFinder.startLookup();
130                assertTrue(mMailboxFinder.isStartedForTest());
131            }
132        });
133    }
134
135    /**
136     * Wait until any of the {@link MailboxFinder.Callback} method or
137     * {@link Controller#updateMailboxList} is called.
138     */
139    private void waitUntilCallbackCalled() {
140        TestUtils.waitUntil("", new TestUtils.Condition() {
141            @Override
142            public boolean isMet() {
143                return mCallback.isAnyMethodCalled() || mMockController.mCalledUpdateMailboxList;
144            }
145        }, TIMEOUT);
146    }
147
148    /**
149     * Test: Account is on security hold.
150     */
151    public void testSecurityHold() throws Throwable {
152        final long accountId = createAccount(true);
153
154        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
155        waitUntilCallbackCalled();
156
157        assertFalse(mCallback.mCalledAccountNotFound);
158        assertTrue(mCallback.mCalledAccountSecurityHold);
159        assertFalse(mCallback.mCalledMailboxFound);
160        assertFalse(mCallback.mCalledMailboxNotFound);
161        assertFalse(mMockController.mCalledUpdateMailboxList);
162
163        assertTrue(mMailboxFinder.isReallyClosedForTest());
164    }
165
166    /**
167     * Test: Account does not exist.
168     */
169    public void testAccountNotFound() throws Throwable {
170        createAndStartFinder(123456, Mailbox.TYPE_INBOX); // No such account.
171        waitUntilCallbackCalled();
172
173        assertTrue(mCallback.mCalledAccountNotFound);
174        assertFalse(mCallback.mCalledAccountSecurityHold);
175        assertFalse(mCallback.mCalledMailboxFound);
176        assertFalse(mCallback.mCalledMailboxNotFound);
177        assertFalse(mMockController.mCalledUpdateMailboxList);
178
179        assertTrue(mMailboxFinder.isReallyClosedForTest());
180    }
181
182    /**
183     * Test: Mailbox found
184     */
185    public void testMailboxFound() throws Throwable {
186        final long accountId = createAccount(false);
187        final long mailboxId = createMailbox(accountId, Mailbox.TYPE_INBOX);
188
189        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
190        waitUntilCallbackCalled();
191
192        assertFalse(mCallback.mCalledAccountNotFound);
193        assertFalse(mCallback.mCalledAccountSecurityHold);
194        assertTrue(mCallback.mCalledMailboxFound);
195        assertFalse(mCallback.mCalledMailboxNotFound);
196        assertFalse(mMockController.mCalledUpdateMailboxList);
197
198        assertEquals(accountId, mCallback.mAccountId);
199        assertEquals(mailboxId, mCallback.mMailboxId);
200
201        assertTrue(mMailboxFinder.isReallyClosedForTest());
202    }
203
204    /**
205     * Common initialization for tests that involves network-lookup.
206     */
207    private void prepareForNetworkLookupTest(final long accountId) throws Throwable {
208        // Look for non-existing mailbox.
209        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
210        waitUntilCallbackCalled();
211
212        // Mailbox not found, so the finder should try network-looking up.
213        assertFalse(mCallback.mCalledAccountNotFound);
214        assertFalse(mCallback.mCalledAccountSecurityHold);
215        assertFalse(mCallback.mCalledMailboxFound);
216        assertFalse(mCallback.mCalledMailboxNotFound);
217
218        // Controller.updateMailboxList() should have been called, with the account id.
219        assertTrue(mMockController.mCalledUpdateMailboxList);
220        assertEquals(accountId, mMockController.mPassedAccountId);
221
222        mMockController.reset();
223
224        assertFalse(mMailboxFinder.isReallyClosedForTest()); // Not closed yet
225    }
226
227    /**
228     * Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
229     * list -> mailbox still doesn't exist.
230     */
231    public void testMailboxNotFound() throws Throwable {
232        final long accountId = createAccount(false);
233
234        prepareForNetworkLookupTest(accountId);
235
236        // Imitate the mCallback...
237        runTestOnUiThread(new Runnable() {
238            @Override
239            public void run() {
240                mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
241                        null, accountId, 100);
242            }
243        });
244
245        // Task should have started, so wait for the response...
246        waitUntilCallbackCalled();
247
248        assertFalse(mCallback.mCalledAccountNotFound);
249        assertFalse(mCallback.mCalledAccountSecurityHold);
250        assertFalse(mCallback.mCalledMailboxFound);
251        assertTrue(mCallback.mCalledMailboxNotFound);
252        assertFalse(mMockController.mCalledUpdateMailboxList);
253
254        assertTrue(mMailboxFinder.isReallyClosedForTest());
255    }
256
257    /**
258     * Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
259     * list -> found mailbox this time.
260     */
261    public void testMailboxFoundOnNetwork() throws Throwable {
262        final long accountId = createAccount(false);
263
264        prepareForNetworkLookupTest(accountId);
265
266        // Create mailbox at this point.
267        final long mailboxId = createMailbox(accountId, Mailbox.TYPE_INBOX);
268
269        // Imitate the mCallback...
270        runTestOnUiThread(new Runnable() {
271            @Override
272            public void run() {
273                mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
274                        null, accountId, 100);
275            }
276        });
277
278        // Task should have started, so wait for the response...
279        waitUntilCallbackCalled();
280
281        assertFalse(mCallback.mCalledAccountNotFound);
282        assertFalse(mCallback.mCalledAccountSecurityHold);
283        assertTrue(mCallback.mCalledMailboxFound);
284        assertFalse(mCallback.mCalledMailboxNotFound);
285        assertFalse(mMockController.mCalledUpdateMailboxList);
286
287        assertEquals(accountId, mCallback.mAccountId);
288        assertEquals(mailboxId, mCallback.mMailboxId);
289
290        assertTrue(mMailboxFinder.isReallyClosedForTest());
291    }
292
293    /**
294     * Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
295     * list -> network error.
296     */
297    public void testMailboxNotFoundNetworkError() throws Throwable {
298        final long accountId = createAccount(false);
299
300        prepareForNetworkLookupTest(accountId);
301
302        // Imitate the mCallback...
303        runTestOnUiThread(new Runnable() {
304            @Override
305            public void run() {
306                // network error.
307                mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
308                        new MessagingException("Network error"), accountId, 0);
309            }
310        });
311
312        assertFalse(mCallback.mCalledAccountNotFound);
313        assertFalse(mCallback.mCalledAccountSecurityHold);
314        assertFalse(mCallback.mCalledMailboxFound);
315        assertTrue(mCallback.mCalledMailboxNotFound);
316        assertFalse(mMockController.mCalledUpdateMailboxList);
317
318        assertTrue(mMailboxFinder.isReallyClosedForTest());
319    }
320
321    /**
322     * Test: updateMailboxListCallback won't respond to update of a non-target account.
323     */
324    public void testUpdateMailboxListCallbackNonTarget() throws Throwable {
325        final long accountId = createAccount(false);
326
327        prepareForNetworkLookupTest(accountId);
328
329        // Callback from Controller, but for a different account.
330        runTestOnUiThread(new Runnable() {
331            @Override
332            public void run() {
333                long nonTargetAccountId = accountId + 1;
334                mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
335                        new MessagingException("Network error"), nonTargetAccountId, 0);
336            }
337        });
338
339        // Nothing happened.
340        assertFalse(mCallback.mCalledAccountNotFound);
341        assertFalse(mCallback.mCalledAccountSecurityHold);
342        assertFalse(mCallback.mCalledMailboxFound);
343        assertFalse(mCallback.mCalledMailboxNotFound);
344        assertFalse(mMockController.mCalledUpdateMailboxList);
345
346        assertFalse(mMailboxFinder.isReallyClosedForTest()); // Not closed yet
347    }
348
349    /**
350     * Test: Mailbox not found (mailbox of different type exists)
351     */
352    public void testMailboxNotFound2() throws Throwable {
353        final long accountId = createAccount(false);
354        final long mailboxId = createMailbox(accountId, Mailbox.TYPE_DRAFTS);
355
356        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
357        waitUntilCallbackCalled();
358
359        assertFalse(mCallback.mCalledAccountNotFound);
360        assertFalse(mCallback.mCalledAccountSecurityHold);
361        assertFalse(mCallback.mCalledMailboxFound);
362        assertFalse(mCallback.mCalledMailboxNotFound);
363        assertTrue(mMockController.mCalledUpdateMailboxList);
364
365        assertFalse(mMailboxFinder.isReallyClosedForTest()); // Not closed yet -- network lookup.
366    }
367
368    /**
369     * Test: Call {@link MailboxFinder#startLookup()} twice, which should throw an ISE.
370     */
371    public void testRunTwice() throws Throwable {
372        final long accountId = createAccount(true);
373
374        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
375        try {
376            mMailboxFinder.startLookup();
377            fail("Expected exception not thrown");
378        } catch (IllegalStateException ok) {
379        }
380    }
381
382    public void testCancel() throws Throwable {
383        final long accountId = createAccount(true);
384
385        createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
386        mMailboxFinder.cancel();
387        assertTrue(mMailboxFinder.isReallyClosedForTest());
388    }
389
390    /**
391     * A {@link Controller} that remembers if updateMailboxList has been called.
392     */
393    private static class MockController extends Controller {
394        public volatile long mPassedAccountId;
395        public volatile boolean mCalledUpdateMailboxList;
396
397        public void reset() {
398            mPassedAccountId = -1;
399            mCalledUpdateMailboxList = false;
400        }
401
402        protected MockController(Context context) {
403            super(context);
404        }
405
406        @Override
407        public void updateMailboxList(long accountId) {
408            mCalledUpdateMailboxList = true;
409            mPassedAccountId = accountId;
410        }
411    }
412
413    /**
414     * Callback that logs what method is called with what arguments.
415     */
416    private static class MockCallback implements MailboxFinder.Callback {
417        public volatile boolean mCalledAccountNotFound;
418        public volatile boolean mCalledAccountSecurityHold;
419        public volatile boolean mCalledMailboxFound;
420        public volatile boolean mCalledMailboxNotFound;
421
422        public volatile long mAccountId = -1;
423        public volatile long mMailboxId = -1;
424
425        public boolean isAnyMethodCalled() {
426            return mCalledAccountNotFound || mCalledAccountSecurityHold || mCalledMailboxFound
427                    || mCalledMailboxNotFound;
428        }
429
430        @Override
431        public void onAccountNotFound() {
432            mCalledAccountNotFound = true;
433        }
434
435        @Override
436        public void onAccountSecurityHold(long accountId) {
437            mCalledAccountSecurityHold = true;
438            mAccountId = accountId;
439        }
440
441        @Override
442        public void onMailboxFound(long accountId, long mailboxId) {
443            mCalledMailboxFound = true;
444            mAccountId = accountId;
445            mMailboxId = mailboxId;
446        }
447
448        @Override
449        public void onMailboxNotFound(long accountId) {
450            mCalledMailboxNotFound = true;
451            mAccountId = accountId;
452        }
453    }
454}
455