EditEventHelperTest.java revision a7c0390d9c5dd4ff730de505682687fae5f5ced0
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.calendar.event;
18
19import com.android.calendar.AbstractCalendarActivity;
20import com.android.calendar.AsyncQueryService;
21import com.android.calendar.CalendarEventModel;
22import com.android.calendar.CalendarEventModel.ReminderEntry;
23import com.android.calendar.R;
24import com.android.calendar.event.EventViewUtils;
25
26import android.content.ContentProvider;
27import android.content.ContentProviderOperation;
28import android.content.ContentProviderResult;
29import android.content.ContentValues;
30import android.content.res.Resources;
31import android.database.Cursor;
32import android.database.MatrixCursor;
33import android.net.Uri;
34import android.provider.CalendarContract.Attendees;
35import android.provider.CalendarContract.Events;
36import android.provider.CalendarContract.Reminders;
37import android.test.AndroidTestCase;
38import android.test.mock.MockResources;
39import android.test.suitebuilder.annotation.SmallTest;
40import android.test.suitebuilder.annotation.Smoke;
41import android.text.TextUtils;
42import android.text.format.DateUtils;
43import android.text.format.Time;
44import android.text.util.Rfc822Token;
45
46import java.util.ArrayList;
47import java.util.Calendar;
48import java.util.LinkedHashSet;
49import java.util.TimeZone;
50
51public class EditEventHelperTest extends AndroidTestCase {
52    private static final int TEST_EVENT_ID = 1;
53    private static final int TEST_EVENT_INDEX_ID = 0;
54    private static final long TEST_END = 1272931200000L;
55    private static long TEST_END2 = 1272956400000L;
56    private static final long TEST_START = 1272844800000L;
57    private static long TEST_START2 = 1272870000000L;
58    private static final String LOCAL_TZ = TimeZone.getDefault().getID();
59
60    private static final int SAVE_EVENT_NEW_EVENT = 1;
61    private static final int SAVE_EVENT_MOD_RECUR = 2;
62    private static final int SAVE_EVENT_RECUR_TO_NORECUR = 3;
63    private static final int SAVE_EVENT_NORECUR_TO_RECUR= 4;
64    private static final int SAVE_EVENT_MOD_NORECUR = 5;
65    private static final int SAVE_EVENT_MOD_INSTANCE = 6;
66    private static final int SAVE_EVENT_ALLFOLLOW_TO_NORECUR = 7;
67    private static final int SAVE_EVENT_FIRST_TO_NORECUR = 8;
68    private static final int SAVE_EVENT_FIRST_TO_RECUR = 9;
69    private static final int SAVE_EVENT_ALLFOLLOW_TO_RECUR = 10;
70
71    private static String[] TEST_CURSOR_DATA = new String[] {
72            Integer.toString(TEST_EVENT_ID), // 0 _id
73            "The Question", // 1 title
74            "Evaluating Life, the Universe, and Everything",// 2 description
75            "Earth Mk2", // 3 location
76            "1", // 4 All Day
77            "0", // 5 Has alarm
78            "2", // 6 Calendar id
79            "1272844800000", // 7 dtstart, Monday, May 3rd midnight UTC
80            "1272931200000", // 8 dtend, Tuesday, May 4th midnight UTC
81            "P3652421990D", // 9 duration, (10 million years)
82            "UTC", // 10 event timezone
83            "FREQ=DAILY;WKST=SU", // 11 rrule
84            "unique per calendar stuff", // 12 sync id
85            "0", // 13 transparency
86            "3", // 14 visibility
87            "steve@gmail.com", // 15 owner account
88            "1", // 16 has attendee data
89            null, //17 originalEvent
90    }; // These should match up with EditEventHelper.EVENT_PROJECTION
91
92    private static final String AUTHORITY_URI = "content://EditEventHelperAuthority/";
93    private static final String AUTHORITY = "EditEventHelperAuthority";
94
95    private static final String TEST_ADDRESSES =
96            "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " +
97            "one.two.three@email.grue";
98    private static final String TEST_ADDRESSES2 =
99            "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " +
100            "different@email.bit";
101
102
103    private static final String TAG = "EEHTest";
104
105    private CalendarEventModel mModel1;
106    private CalendarEventModel mModel2;
107
108    private ContentValues mValues;
109    private ContentValues mExpectedValues;
110
111    private EditEventHelper mHelper;
112    private AbstractCalendarActivity mActivity;
113    private int mCurrentSaveTest = 0;
114
115    @Override
116    public void setUp() {
117        Time time = new Time(Time.TIMEZONE_UTC);
118        time.set(TEST_START);
119        time.timezone = LOCAL_TZ;
120        TEST_START2 = time.normalize(true);
121
122        time.timezone = Time.TIMEZONE_UTC;
123        time.set(TEST_END);
124        time.timezone = LOCAL_TZ;
125        TEST_END2 = time.normalize(true);
126    }
127
128    private class MockAbsCalendarActivity extends AbstractCalendarActivity {
129        @Override
130        public AsyncQueryService getAsyncQueryService() {
131            if (mService == null) {
132                mService = new AsyncQueryService(this) {
133                    @Override
134                    public void startBatch(int token, Object cookie, String authority,
135                            ArrayList<ContentProviderOperation> cpo, long delayMillis) {
136                        mockApplyBatch(authority, cpo);
137                    }
138                };
139            }
140            return mService;
141        }
142
143        @Override
144        public Resources getResources() {
145            Resources res = new MockResources() {
146                @Override
147                // The actual selects singular vs plural as well and in the given language
148                public String getQuantityString(int id, int quantity) {
149                    if (id == R.plurals.Nmins) {
150                        return quantity + " mins";
151                    }
152                    if (id == R.plurals.Nminutes) {
153                        return quantity + " minutes";
154                    }
155                    if (id == R.plurals.Nhours) {
156                        return quantity + " hours";
157                    }
158                    if (id == R.plurals.Ndays) {
159                        return quantity + " days";
160                    }
161                    return id + " " + quantity;
162                }
163            };
164            return res;
165        }
166    }
167
168    private AbstractCalendarActivity buildTestContext() {
169        MockAbsCalendarActivity context = new MockAbsCalendarActivity();
170        return context;
171    }
172
173    private ContentProviderResult[] mockApplyBatch(String authority,
174            ArrayList<ContentProviderOperation> operations) {
175        switch (mCurrentSaveTest) {
176            case SAVE_EVENT_NEW_EVENT:
177                // new recurring event
178                verifySaveEventNewEvent(operations);
179                break;
180            case SAVE_EVENT_MOD_RECUR:
181                // update to recurring event
182                verifySaveEventModifyRecurring(operations);
183                break;
184            case SAVE_EVENT_RECUR_TO_NORECUR:
185                // replace recurring event with non-recurring event
186                verifySaveEventRecurringToNonRecurring(operations);
187                break;
188            case SAVE_EVENT_NORECUR_TO_RECUR:
189                // update non-recurring event with recurring event
190                verifySaveEventNonRecurringToRecurring(operations);
191                break;
192            case SAVE_EVENT_MOD_NORECUR:
193                // update to non-recurring
194                verifySaveEventUpdateNonRecurring(operations);
195                break;
196            case SAVE_EVENT_MOD_INSTANCE:
197                // update to single instance of recurring event
198                verifySaveEventModifySingleInstance(operations);
199                break;
200            case SAVE_EVENT_ALLFOLLOW_TO_NORECUR:
201                // update all following with non-recurring event
202                verifySaveEventModifyAllFollowingWithNonRecurring(operations);
203                break;
204            case SAVE_EVENT_FIRST_TO_NORECUR:
205                // update all following with non-recurring event on first event in series
206                verifySaveEventModifyAllFollowingFirstWithNonRecurring(operations);
207                break;
208            case SAVE_EVENT_FIRST_TO_RECUR:
209                // update all following with recurring event on first event in series
210                verifySaveEventModifyAllFollowingFirstWithRecurring(operations);
211                break;
212            case SAVE_EVENT_ALLFOLLOW_TO_RECUR:
213                // update all following with recurring event on second event in series
214                verifySaveEventModifyAllFollowingWithRecurring(operations);
215                break;
216        }
217        return new ContentProviderResult[] {new ContentProviderResult(5)};
218    }
219
220    private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps, int id) {
221        addOwnerAttendee();
222        ContentProviderOperation.Builder b;
223        b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues);
224        b.withValueBackReference(Reminders.EVENT_ID, id);
225        expectedOps.add(b.build());
226    }
227
228    // Some tests set the time values to one day later, this does that move in the values
229    private void moveExpectedTimeValuesForwardOneDay() {
230        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
231        mExpectedValues.put(Events.DTSTART, TEST_START + dayInMs);
232        mExpectedValues.put(Events.DTEND, TEST_END + dayInMs);
233    }
234
235    // Duplicates the delete and add for changing a single email address
236    private void addAttendeeChangesOps(ArrayList<ContentProviderOperation> expectedOps) {
237        ContentProviderOperation.Builder b =
238            ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
239        b.withSelection(EditEventHelper.ATTENDEES_DELETE_PREFIX + "?)",
240                new String[] {"one.two.three@email.grue"});
241        expectedOps.add(b.build());
242
243        mExpectedValues.clear();
244        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
245        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit");
246        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
247        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
248        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
249        mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID);
250        b = ContentProviderOperation
251                .newInsert(Attendees.CONTENT_URI)
252                .withValues(mExpectedValues);
253        expectedOps.add(b.build());
254    }
255
256    // This is a commonly added set of values
257    private void addOwnerAttendee() {
258        mExpectedValues.clear();
259        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, mModel1.mOwnerAccount);
260        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
261        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
262        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
263    }
264
265    /** Some tests add all the attendees to the db, the names and emails should match
266     * with {@link #TEST_ADDRESSES2} minus the 'no good'
267     */
268    private void addTestAttendees(ArrayList<ContentProviderOperation> ops,
269            boolean newEvent, int id) {
270        ContentProviderOperation.Builder b;
271        mExpectedValues.clear();
272        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
273        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "ad1@email.com");
274        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
275        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
276        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
277
278        if (newEvent) {
279            b = ContentProviderOperation
280                    .newInsert(Attendees.CONTENT_URI)
281                    .withValues(mExpectedValues);
282            b.withValueBackReference(Attendees.EVENT_ID, id);
283        } else {
284            mExpectedValues.put(Attendees.EVENT_ID, id);
285            b = ContentProviderOperation
286                    .newInsert(Attendees.CONTENT_URI)
287                    .withValues(mExpectedValues);
288        }
289        ops.add(b.build());
290
291        mExpectedValues.clear();
292        mExpectedValues.put(Attendees.ATTENDEE_NAME, "First Last");
293        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "first@email.com");
294        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
295        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
296        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
297
298        if (newEvent) {
299            b = ContentProviderOperation
300                    .newInsert(Attendees.CONTENT_URI)
301                    .withValues(mExpectedValues);
302            b.withValueBackReference(Attendees.EVENT_ID, id);
303        } else {
304            mExpectedValues.put(Attendees.EVENT_ID, id);
305            b = ContentProviderOperation
306                    .newInsert(Attendees.CONTENT_URI)
307                    .withValues(mExpectedValues);
308        }
309        ops.add(b.build());
310
311        mExpectedValues.clear();
312        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
313        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit");
314        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
315        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
316        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
317
318        if (newEvent) {
319            b = ContentProviderOperation
320                    .newInsert(Attendees.CONTENT_URI)
321                    .withValues(mExpectedValues);
322            b.withValueBackReference(Attendees.EVENT_ID, id);
323        } else {
324            mExpectedValues.put(Attendees.EVENT_ID, id);
325            b = ContentProviderOperation
326                    .newInsert(Attendees.CONTENT_URI)
327                    .withValues(mExpectedValues);
328        }
329        ops.add(b.build());
330    }
331
332    @Smoke
333    @SmallTest
334    public void testSaveEventFailures() {
335        mActivity = buildTestContext();
336        mHelper = new EditEventHelper(mActivity, null);
337
338        mModel1 = buildTestModel();
339        mModel2 = buildTestModel();
340
341        // saveEvent should return false early if:
342        // -it was set to not ok
343        // -the model was null
344        // -the event doesn't represent the same event as the original event
345        // -there's a uri but an original event is not provided
346        mHelper.mEventOk = false;
347        assertFalse(mHelper.saveEvent(null, null, 0));
348        mHelper.mEventOk = true;
349        assertFalse(mHelper.saveEvent(null, null, 0));
350        mModel2.mId = 13;
351        assertFalse(mHelper.saveEvent(mModel1, mModel2, 0));
352        mModel2.mId = mModel1.mId;
353        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
354        mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
355        assertFalse(mHelper.saveEvent(mModel1, null, 0));
356    }
357
358    @Smoke
359    @SmallTest
360    public void testSaveEventNewEvent() {
361        // Creates a model of a new event for saving
362        mActivity = buildTestContext();
363        mHelper = new EditEventHelper(mActivity, null);
364
365        mModel1 = buildTestModel();
366//        mModel1.mAttendees = TEST_ADDRESSES2;
367        mCurrentSaveTest = SAVE_EVENT_NEW_EVENT;
368
369        assertTrue(mHelper.saveEvent(mModel1, null, 0));
370    }
371
372    private boolean verifySaveEventNewEvent(ArrayList<ContentProviderOperation> ops) {
373        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
374        int br_id = 0;
375        mExpectedValues = buildTestValues();
376        mExpectedValues.put(Events.HAS_ALARM, 0);
377        mExpectedValues.put(Events.HAS_ATTENDEE_DATA, 1);
378        ContentProviderOperation.Builder b = ContentProviderOperation
379                .newInsert(Events.CONTENT_URI)
380                .withValues(mExpectedValues);
381        expectedOps.add(b.build());
382
383        // This call has a separate unit test so we'll use it to simplify making the expected vals
384        mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders,
385                new ArrayList<ReminderEntry>(), true);
386
387        addOwnerAttendeeToOps(expectedOps, br_id);
388
389        addTestAttendees(expectedOps, true, br_id);
390
391        assertEquals(ops, expectedOps);
392        return true;
393    }
394
395    @Smoke
396    @SmallTest
397    public void testSaveEventModifyRecurring() {
398        // Creates an original and an updated recurring event model
399        mActivity = buildTestContext();
400        mHelper = new EditEventHelper(mActivity, null);
401
402        mModel1 = buildTestModel();
403        mModel2 = buildTestModel();
404//        mModel1.mAttendees = TEST_ADDRESSES2;
405
406        // Updating a recurring event with a new attendee list
407        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
408        // And a new start time to ensure the time fields aren't removed
409        mModel1.mOriginalStart = TEST_START;
410
411        // The original model is assumed correct so drop the no good bit
412//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
413//            "one.two.three@email.grue";
414        mCurrentSaveTest = SAVE_EVENT_MOD_RECUR;
415
416        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
417    }
418
419    private boolean verifySaveEventModifyRecurring(ArrayList<ContentProviderOperation> ops) {
420        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
421        int br_id = 0;
422        mExpectedValues = buildTestValues();
423        mExpectedValues.put(Events.HAS_ALARM, 0);
424        // This is tested elsewhere, used for convenience here
425        mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues,
426                EditEventHelper.MODIFY_ALL);
427
428        expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(
429                mExpectedValues).build());
430
431        // This call has a separate unit test so we'll use it to simplify making the expected vals
432        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders,
433                mModel2.mReminders, false);
434
435        addAttendeeChangesOps(expectedOps);
436
437        assertEquals(ops, expectedOps);
438        return true;
439    }
440
441    @Smoke
442    @SmallTest
443    public void testSaveEventRecurringToNonRecurring() {
444        // Creates an original and an updated recurring event model
445        mActivity = buildTestContext();
446        mHelper = new EditEventHelper(mActivity, null);
447
448        mModel1 = buildTestModel();
449        mModel2 = buildTestModel();
450//        mModel1.mAttendees = TEST_ADDRESSES2;
451
452        // Updating a recurring event with a new attendee list
453        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
454        // And a new start time to ensure the time fields aren't removed
455        mModel1.mOriginalStart = TEST_START;
456
457        // The original model is assumed correct so drop the no good bit
458//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
459//            "one.two.three@email.grue";
460
461        // Replace an existing recurring event with a non-recurring event
462        mModel1.mRrule = null;
463        mModel1.mEnd = TEST_END;
464        mCurrentSaveTest = SAVE_EVENT_RECUR_TO_NORECUR;
465
466        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
467    }
468
469    private boolean verifySaveEventRecurringToNonRecurring(ArrayList<ContentProviderOperation> ops)
470            {
471        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
472        int id = 0;
473        mExpectedValues = buildNonRecurringTestValues();
474        mExpectedValues.put(Events.HAS_ALARM, 0);
475        // This is tested elsewhere, used for convenience here
476        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
477                EditEventHelper.MODIFY_ALL);
478
479        expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build());
480        id = expectedOps.size();
481        expectedOps.add(ContentProviderOperation
482                        .newInsert(Events.CONTENT_URI)
483                        .withValues(mExpectedValues)
484                        .build());
485
486        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders,
487                mModel2.mReminders, true);
488
489        addOwnerAttendeeToOps(expectedOps, id);
490
491        addTestAttendees(expectedOps, true, id);
492
493        assertEquals(ops, expectedOps);
494        return true;
495    }
496
497    @Smoke
498    @SmallTest
499    public void testSaveEventNonRecurringToRecurring() {
500        // Creates an original non-recurring and an updated recurring event model
501        mActivity = buildTestContext();
502        mHelper = new EditEventHelper(mActivity, null);
503
504        mModel1 = buildTestModel();
505        mModel2 = buildTestModel();
506//        mModel1.mAttendees = TEST_ADDRESSES2;
507
508        // Updating a recurring event with a new attendee list
509        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
510        // And a new start time to ensure the time fields aren't removed
511        mModel1.mOriginalStart = TEST_START;
512
513        // The original model is assumed correct so drop the no good bit
514//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
515//            "one.two.three@email.grue";
516
517        mModel2.mRrule = null;
518        mModel2.mEnd = TEST_END;
519        mCurrentSaveTest = SAVE_EVENT_NORECUR_TO_RECUR;
520
521        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
522    }
523
524    private boolean verifySaveEventNonRecurringToRecurring(ArrayList<ContentProviderOperation> ops)
525            {
526        // Changing a non-recurring event to a recurring event should generate the same operations
527        // as just modifying a recurring event.
528        return verifySaveEventModifyRecurring(ops);
529    }
530
531    @Smoke
532    @SmallTest
533    public void testSaveEventUpdateNonRecurring() {
534        // Creates an original non-recurring and an updated recurring event model
535        mActivity = buildTestContext();
536        mHelper = new EditEventHelper(mActivity, null);
537
538        mModel1 = buildTestModel();
539        mModel2 = buildTestModel();
540//        mModel1.mAttendees = TEST_ADDRESSES2;
541
542        // Updating a recurring event with a new attendee list
543        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
544        // And a new start time to ensure the time fields aren't removed
545        mModel1.mOriginalStart = TEST_START;
546
547        // The original model is assumed correct so drop the no good bit
548//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
549//            "one.two.three@email.grue";
550
551        mModel2.mRrule = null;
552        mModel2.mEnd = TEST_END2;
553        mModel1.mRrule = null;
554        mModel1.mEnd = TEST_END2;
555        mCurrentSaveTest = SAVE_EVENT_MOD_NORECUR;
556
557        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
558    }
559
560    private boolean verifySaveEventUpdateNonRecurring(ArrayList<ContentProviderOperation> ops) {
561        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
562        int id = TEST_EVENT_ID;
563        mExpectedValues = buildNonRecurringTestValues();
564        mExpectedValues.put(Events.HAS_ALARM, 0);
565        // This is tested elsewhere, used for convenience here
566        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
567                EditEventHelper.MODIFY_ALL);
568        expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(
569                mExpectedValues).build());
570        // This call has a separate unit test so we'll use it to simplify making the expected vals
571        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders,
572                mModel2.mReminders, false);
573        addAttendeeChangesOps(expectedOps);
574
575        assertEquals(ops, expectedOps);
576        return true;
577    }
578
579    @Smoke
580    @SmallTest
581    public void testSaveEventModifySingleInstance() {
582        // Creates an original non-recurring and an updated recurring event model
583        mActivity = buildTestContext();
584        mHelper = new EditEventHelper(mActivity, null);
585
586        mModel1 = buildTestModel();
587        mModel2 = buildTestModel();
588//        mModel1.mAttendees = TEST_ADDRESSES2;
589
590        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
591        // And a new start time to ensure the time fields aren't removed
592        mModel1.mOriginalStart = TEST_START;
593
594        // The original model is assumed correct so drop the no good bit
595//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
596//            "one.two.three@email.grue";
597
598        // Modify the second instance of the event
599        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
600        mModel1.mRrule = null;
601        mModel1.mEnd = TEST_END + dayInMs;
602        mModel1.mStart += dayInMs;
603        mModel1.mOriginalStart = mModel1.mStart;
604
605        mCurrentSaveTest = SAVE_EVENT_MOD_INSTANCE;
606        // Only modify this instance
607        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_SELECTED));
608    }
609
610    private boolean verifySaveEventModifySingleInstance(ArrayList<ContentProviderOperation> ops) {
611        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
612        int id = 0;
613        mExpectedValues = buildNonRecurringTestValues();
614        mExpectedValues.put(Events.HAS_ALARM, 0);
615        // This is tested elsewhere, used for convenience here
616        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
617                EditEventHelper.MODIFY_ALL);
618
619        moveExpectedTimeValuesForwardOneDay();
620        mExpectedValues.put(Events.ORIGINAL_SYNC_ID, mModel2.mSyncId);
621        mExpectedValues.put(Events.ORIGINAL_INSTANCE_TIME, mModel1.mOriginalStart);
622        mExpectedValues.put(Events.ORIGINAL_ALL_DAY, 1);
623
624        ContentProviderOperation.Builder b = ContentProviderOperation
625                .newInsert(Events.CONTENT_URI)
626                .withValues(mExpectedValues);
627        expectedOps.add(b.build());
628
629        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders,
630                mModel2.mReminders, true);
631
632        addOwnerAttendeeToOps(expectedOps, id);
633
634        addTestAttendees(expectedOps, true, id);
635
636        assertEquals(ops, expectedOps);
637        return true;
638    }
639
640    @Smoke
641    @SmallTest
642    public void testSaveEventModifyAllFollowingWithNonRecurring() {
643        // Creates an original and an updated recurring event model. The update starts on the 2nd
644        // instance of the original.
645        mActivity = buildTestContext();
646        mHelper = new EditEventHelper(mActivity, null);
647
648        mModel1 = buildTestModel();
649        mModel2 = buildTestModel();
650//        mModel1.mAttendees = TEST_ADDRESSES2;
651
652        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
653        mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
654
655        // The original model is assumed correct so drop the no good bit
656//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
657//            "one.two.three@email.grue";
658
659        // Modify the second instance of the event
660        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
661        mModel1.mRrule = null;
662        mModel1.mEnd = TEST_END + dayInMs;
663        mModel1.mStart += dayInMs;
664        mModel1.mOriginalStart = mModel1.mStart;
665
666        mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_NORECUR;
667        // Only modify this instance
668        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
669    }
670
671    private boolean verifySaveEventModifyAllFollowingWithNonRecurring(
672            ArrayList<ContentProviderOperation> ops) {
673        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
674        int id = 0;
675        mExpectedValues = buildNonRecurringTestValues();
676        mExpectedValues.put(Events.HAS_ALARM, 0);
677        moveExpectedTimeValuesForwardOneDay();
678        // This has a separate test
679        mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart);
680        id = expectedOps.size();
681        expectedOps.add(ContentProviderOperation
682                .newInsert(Events.CONTENT_URI)
683                .withValues(mExpectedValues)
684                .build());
685
686        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders,
687                mModel2.mReminders, true);
688
689        addOwnerAttendeeToOps(expectedOps, id);
690
691        addTestAttendees(expectedOps, true, id);
692
693        assertEquals(ops, expectedOps);
694        return true;
695    }
696
697    @Smoke
698    @SmallTest
699    public void testSaveEventModifyAllFollowingFirstWithNonRecurring() {
700        // Creates an original recurring and an updated non-recurring event model for the first
701        // instance. This should replace the original event with a non-recurring event.
702        mActivity = buildTestContext();
703        mHelper = new EditEventHelper(mActivity, null);
704
705        mModel1 = buildTestModel();
706        mModel2 = buildTestModel();
707//        mModel1.mAttendees = TEST_ADDRESSES2;
708
709        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
710        mModel2.mUri = mModel1.mUri;
711        // And a new start time to ensure the time fields aren't removed
712        mModel1.mOriginalStart = TEST_START;
713
714        // The original model is assumed correct so drop the no good bit
715//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
716//            "one.two.three@email.grue";
717
718        // Move the event one day but keep original start set to the first instance
719        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
720        mModel1.mRrule = null;
721        mModel1.mEnd = TEST_END + dayInMs;
722        mModel1.mStart += dayInMs;
723
724        mCurrentSaveTest = SAVE_EVENT_FIRST_TO_NORECUR;
725        // Only modify this instance
726        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
727    }
728
729    private boolean verifySaveEventModifyAllFollowingFirstWithNonRecurring(
730            ArrayList<ContentProviderOperation> ops) {
731
732        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
733        int id = 0;
734        mExpectedValues = buildNonRecurringTestValues();
735        mExpectedValues.put(Events.HAS_ALARM, 0);
736        moveExpectedTimeValuesForwardOneDay();
737
738        expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build());
739        id = expectedOps.size();
740        expectedOps.add(ContentProviderOperation
741                        .newInsert(Events.CONTENT_URI)
742                        .withValues(mExpectedValues)
743                        .build());
744
745        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders,
746                mModel2.mReminders, true);
747
748        addOwnerAttendeeToOps(expectedOps, id);
749
750        addTestAttendees(expectedOps, true, id);
751
752        assertEquals(ops, expectedOps);
753        return true;
754    }
755
756    @Smoke
757    @SmallTest
758    public void testSaveEventModifyAllFollowingFirstWithRecurring() {
759        // Creates an original recurring and an updated recurring event model for the first instance
760        // This should replace the original event with a new recurrence
761        mActivity = buildTestContext();
762        mHelper = new EditEventHelper(mActivity, null);
763
764        mModel1 = buildTestModel();
765        mModel2 = buildTestModel();
766//        mModel1.mAttendees = TEST_ADDRESSES2;
767
768        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
769        mModel2.mUri = mModel1.mUri;
770        // And a new start time to ensure the time fields aren't removed
771        mModel1.mOriginalStart = TEST_START;
772
773        // The original model is assumed correct so drop the no good bit
774//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
775//            "one.two.three@email.grue";
776
777        // Move the event one day but keep original start set to the first instance
778        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
779        mModel1.mStart += dayInMs;
780
781        mCurrentSaveTest = SAVE_EVENT_FIRST_TO_RECUR;
782        // Only modify this instance
783        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
784    }
785
786    private boolean verifySaveEventModifyAllFollowingFirstWithRecurring(
787            ArrayList<ContentProviderOperation> ops) {
788        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
789        int br_id = 0;
790        mExpectedValues = buildTestValues();
791        mExpectedValues.put(Events.HAS_ALARM, 0);
792        moveExpectedTimeValuesForwardOneDay();
793        mExpectedValues.put(Events.DTEND, (Long)null);
794        // This is tested elsewhere, used for convenience here
795        mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues,
796                EditEventHelper.MODIFY_ALL_FOLLOWING);
797
798        expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(
799                mExpectedValues).build());
800
801        // This call has a separate unit test so we'll use it to simplify making the expected vals
802        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders,
803                mModel2.mReminders, true);
804
805        addAttendeeChangesOps(expectedOps);
806
807        assertEquals(ops, expectedOps);
808        return true;
809    }
810
811    @Smoke
812    @SmallTest
813    public void testSaveEventModifyAllFollowingWithRecurring() {
814        // Creates an original recurring and an updated recurring event model
815        // for the second instance. This should end the original recurrence and add a new
816        // recurrence.
817        mActivity = buildTestContext();
818        mHelper = new EditEventHelper(mActivity, null);
819
820        mModel1 = buildTestModel();
821        mModel2 = buildTestModel();
822//        mModel1.mAttendees = TEST_ADDRESSES2;
823
824        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
825        mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
826
827        // The original model is assumed correct so drop the no good bit
828//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
829//            "one.two.three@email.grue";
830
831        // Move the event one day and the original start so it references the second instance
832        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
833        mModel1.mStart += dayInMs;
834        mModel1.mOriginalStart = mModel1.mStart;
835
836        mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_RECUR;
837        // Only modify this instance
838        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
839    }
840
841    private boolean verifySaveEventModifyAllFollowingWithRecurring(
842            ArrayList<ContentProviderOperation> ops) {
843        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
844        int br_id = 0;
845        mExpectedValues = buildTestValues();
846        mExpectedValues.put(Events.HAS_ALARM, 0);
847        moveExpectedTimeValuesForwardOneDay();
848        mExpectedValues.put(Events.DTEND, (Long)null);
849        // This is tested elsewhere, used for convenience here
850        mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart);
851
852        br_id = expectedOps.size();
853        expectedOps.add(ContentProviderOperation
854                .newInsert(Events.CONTENT_URI)
855                .withValues(mExpectedValues)
856                .build());
857
858        // This call has a separate unit test so we'll use it to simplify making the expected vals
859        mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders,
860                mModel2.mReminders, true);
861
862        addOwnerAttendeeToOps(expectedOps, br_id);
863
864        addTestAttendees(expectedOps, true, br_id);
865
866        assertEquals(ops, expectedOps);
867        return true;
868    }
869
870    @Smoke
871    @SmallTest
872    public void testGetAddressesFromList() {
873        mActivity = buildTestContext();
874        mHelper = new EditEventHelper(mActivity, null);
875
876        LinkedHashSet<Rfc822Token> expected = new LinkedHashSet<Rfc822Token>();
877        expected.add(new Rfc822Token(null, "ad1@email.com", ""));
878        expected.add(new Rfc822Token("First Last", "first@email.com", "comment"));
879        expected.add(new Rfc822Token(null, "one.two.three@email.grue", ""));
880
881        LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES, null);
882        assertEquals(actual, expected);
883    }
884
885    @Smoke
886    @SmallTest
887    public void testConstructDefaultStartTime() {
888        mActivity = buildTestContext();
889        mHelper = new EditEventHelper(mActivity, null);
890
891        long now = 0;
892        long expected = now + 30 * DateUtils.MINUTE_IN_MILLIS;
893        assertEquals(expected, mHelper.constructDefaultStartTime(now));
894
895        // 2:00 -> 2:30
896        now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST)
897        expected = now + 30 * DateUtils.MINUTE_IN_MILLIS;
898        assertEquals(expected, mHelper.constructDefaultStartTime(now));
899
900        // 2:01 -> 2:30
901        now += DateUtils.MINUTE_IN_MILLIS;
902        assertEquals(expected, mHelper.constructDefaultStartTime(now));
903
904        // 2:02 -> 2:30
905        now += DateUtils.MINUTE_IN_MILLIS;
906        assertEquals(expected, mHelper.constructDefaultStartTime(now));
907
908        // 2:32 -> 3:00
909        now += 30 * DateUtils.MINUTE_IN_MILLIS;
910        expected += 30 * DateUtils.MINUTE_IN_MILLIS;
911        assertEquals(expected, mHelper.constructDefaultStartTime(now));
912
913        // 2:33 -> 3:00
914        now += DateUtils.MINUTE_IN_MILLIS;
915        assertEquals(expected, mHelper.constructDefaultStartTime(now));
916
917    }
918
919    @Smoke
920    @SmallTest
921    public void testConstructDefaultEndTime() {
922        mActivity = buildTestContext();
923        mHelper = new EditEventHelper(mActivity, null);
924
925        long start = 1262340000000L;
926        long expected = start + DateUtils.HOUR_IN_MILLIS;
927        assertEquals(expected, mHelper.constructDefaultEndTime(start));
928    }
929
930    @Smoke
931    @SmallTest
932    public void testCheckTimeDependentFieldsNoChanges() {
933        mActivity = buildTestContext();
934        mHelper = new EditEventHelper(mActivity, null);
935
936        mModel1 = buildTestModel();
937        mModel2 = buildTestModel();
938        mModel2.mRrule = null;
939
940        mValues = buildTestValues();
941        mExpectedValues = buildTestValues();
942
943        // if any time/recurrence vals are different but there's no new rrule it
944        // shouldn't change
945        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, EditEventHelper.MODIFY_ALL);
946        assertEquals(mValues, mExpectedValues);
947
948        // also, if vals are different and it's not modifying all it shouldn't
949        // change.
950        mModel2.mRrule = "something else";
951        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
952                EditEventHelper.MODIFY_SELECTED);
953        assertEquals(mValues, mExpectedValues);
954
955        // if vals changed and modify all is selected dtstart should be updated
956        // by the difference
957        // between originalStart and start
958        mModel2.mOriginalStart = mModel2.mStart + 60000; // set the old time to
959                                                         // one minute later
960        mModel2.mStart += 120000; // move the event another 1 minute.
961
962        // shouldn't change for an allday event
963        // expectedVals.put(Events.DTSTART, mModel1.mStart + 60000); // should
964        // now be 1 minute later
965        // dtstart2 shouldn't change since it gets rezeroed in the local
966        // timezone for allDay events
967
968        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
969                EditEventHelper.MODIFY_SELECTED);
970        assertEquals(mValues, mExpectedValues);
971    }
972
973    @Smoke
974    @SmallTest
975    public void testCheckTimeDependentFieldsChanges() {
976        mActivity = buildTestContext();
977        mHelper = new EditEventHelper(mActivity, null);
978
979        mModel1 = buildTestModel();
980        mModel2 = buildTestModel();
981        mModel2.mRrule = null;
982
983        mValues = buildTestValues();
984        mExpectedValues = buildTestValues();
985
986        // if all the time values are the same it should remove them from vals
987        mModel2.mRrule = mModel1.mRrule;
988        mModel2.mStart = mModel1.mStart;
989        mModel2.mOriginalStart = mModel2.mStart;
990
991        mExpectedValues.remove(Events.DTSTART);
992        mExpectedValues.remove(Events.DTEND);
993        mExpectedValues.remove(Events.DURATION);
994        mExpectedValues.remove(Events.ALL_DAY);
995        mExpectedValues.remove(Events.RRULE);
996        mExpectedValues.remove(Events.EVENT_TIMEZONE);
997
998        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
999                EditEventHelper.MODIFY_SELECTED);
1000        assertEquals(mValues, mExpectedValues);
1001
1002    }
1003
1004    @Smoke
1005    @SmallTest
1006    public void testUpdatePastEvents() {
1007        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1008        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
1009        long initialBeginTime = 1472864400000L; // Sep 3, 2016, 12AM UTC time
1010        mValues = new ContentValues();
1011
1012        mModel1 = buildTestModel();
1013        mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID);
1014        mActivity = buildTestContext();
1015        mHelper = new EditEventHelper(mActivity, null);
1016
1017        mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903;WKST=SU"); // yyyymmddThhmmssZ
1018        mValues.put(Events.DTSTART, TEST_START);
1019
1020        ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(
1021                Uri.parse(mModel1.mUri)).withValues(mValues);
1022        expectedOps.add(b.build());
1023
1024        mHelper.updatePastEvents(ops, mModel1, initialBeginTime);
1025        assertEquals(ops, expectedOps);
1026
1027        mModel1.mAllDay = false;
1028
1029        mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903T005959Z;WKST=SU"); // yyyymmddThhmmssZ
1030
1031        expectedOps.clear();
1032        b = ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(mValues);
1033        expectedOps.add(b.build());
1034
1035        ops.clear();
1036        mHelper.updatePastEvents(ops, mModel1, initialBeginTime);
1037        assertEquals(ops, expectedOps);
1038    }
1039
1040    @Smoke
1041    @SmallTest
1042    public void testConstructReminderLabel() {
1043        mActivity = buildTestContext();
1044
1045        String label = EventViewUtils.constructReminderLabel(mActivity, 35, true);
1046        assertEquals(label, "35 mins");
1047
1048        label = EventViewUtils.constructReminderLabel(mActivity, 72, false);
1049        assertEquals(label, "72 minutes");
1050
1051        label = EventViewUtils.constructReminderLabel(mActivity, 60, true);
1052        assertEquals(label, "1 hours");
1053
1054        label = EventViewUtils.constructReminderLabel(mActivity, 60 * 48, true);
1055        assertEquals(label, "2 days");
1056    }
1057
1058    @Smoke
1059    @SmallTest
1060    public void testIsSameEvent() {
1061        mModel1 = new CalendarEventModel();
1062        mModel2 = new CalendarEventModel();
1063
1064        mModel1.mId = 1;
1065        mModel1.mCalendarId = 1;
1066        mModel2.mId = 1;
1067        mModel2.mCalendarId = 1;
1068
1069        // considered the same if the event and calendar ids both match
1070        assertTrue(EditEventHelper.isSameEvent(mModel1, mModel2));
1071
1072        mModel2.mId = 2;
1073        assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2));
1074
1075        mModel2.mId = 1;
1076        mModel2.mCalendarId = 2;
1077        assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2));
1078    }
1079
1080    @Smoke
1081    @SmallTest
1082    public void testSaveReminders() {
1083        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1084        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
1085        long eventId = TEST_EVENT_ID;
1086        ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>();
1087        ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>();
1088        boolean forceSave = true;
1089        boolean result;
1090        mActivity = buildTestContext();
1091        mHelper = new EditEventHelper(mActivity, null);
1092        assertNotNull(mHelper);
1093
1094        // First test forcing a delete with no reminders.
1095        String where = Reminders.EVENT_ID + "=?";
1096        String[] args = new String[] {Long.toString(eventId)};
1097        ContentProviderOperation.Builder b =
1098                ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
1099        b.withSelection(where, args);
1100        expectedOps.add(b.build());
1101
1102        result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave);
1103        assertTrue(result);
1104        assertEquals(ops, expectedOps);
1105
1106        // Now test calling save with identical reminders and no forcing
1107        reminders.add(ReminderEntry.valueOf(5));
1108        reminders.add(ReminderEntry.valueOf(10));
1109        reminders.add(ReminderEntry.valueOf(15));
1110
1111        originalReminders.add(ReminderEntry.valueOf(5));
1112        originalReminders.add(ReminderEntry.valueOf(10));
1113        originalReminders.add(ReminderEntry.valueOf(15));
1114
1115        forceSave = false;
1116
1117        ops.clear();
1118
1119        // Should fail to create any ops since nothing changed
1120        result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave);
1121        assertFalse(result);
1122        assertEquals(ops.size(), 0);
1123
1124        //Now test adding a single reminder
1125        originalReminders.remove(2);
1126
1127        addExpectedMinutes(expectedOps);
1128
1129        result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave);
1130        assertTrue(result);
1131        assertEquals(ops, expectedOps);
1132    }
1133
1134    @Smoke
1135    @SmallTest
1136    public void testSaveRemindersWithBackRef() {
1137        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1138        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
1139        long eventId = TEST_EVENT_ID;
1140        ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>();
1141        ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>();
1142        boolean forceSave = true;
1143        boolean result;
1144        mActivity = buildTestContext();
1145        mHelper = new EditEventHelper(mActivity, null);
1146        assertNotNull(mHelper);
1147
1148        // First test forcing a delete with no reminders.
1149        ContentProviderOperation.Builder b =
1150                ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
1151        b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
1152        b.withSelectionBackReference(0, TEST_EVENT_INDEX_ID);
1153        expectedOps.add(b.build());
1154
1155        result =
1156                mHelper.saveRemindersWithBackRef(ops, TEST_EVENT_INDEX_ID, reminders,
1157                        originalReminders, forceSave);
1158        assertTrue(result);
1159        assertEquals(ops, expectedOps);
1160
1161        // Now test calling save with identical reminders and no forcing
1162        reminders.add(ReminderEntry.valueOf(5));
1163        reminders.add(ReminderEntry.valueOf(10));
1164        reminders.add(ReminderEntry.valueOf(15));
1165
1166        originalReminders.add(ReminderEntry.valueOf(5));
1167        originalReminders.add(ReminderEntry.valueOf(10));
1168        originalReminders.add(ReminderEntry.valueOf(15));
1169
1170        forceSave = false;
1171
1172        ops.clear();
1173
1174        result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders,
1175                        forceSave);
1176        assertFalse(result);
1177        assertEquals(ops.size(), 0);
1178
1179        //Now test adding a single reminder
1180        originalReminders.remove(2);
1181
1182        addExpectedMinutesWithBackRef(expectedOps);
1183
1184        result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders,
1185                        forceSave);
1186        assertTrue(result);
1187        assertEquals(ops, expectedOps);
1188    }
1189
1190    @Smoke
1191    @SmallTest
1192    public void testIsFirstEventInSeries() {
1193        mModel1 = new CalendarEventModel();
1194        mModel2 = new CalendarEventModel();
1195
1196        // It's considered the first event if the original start of the new model matches the
1197        // start of the old model
1198        mModel1.mOriginalStart = 100;
1199        mModel1.mStart = 200;
1200        mModel2.mOriginalStart = 100;
1201        mModel2.mStart = 100;
1202
1203        assertTrue(EditEventHelper.isFirstEventInSeries(mModel1, mModel2));
1204
1205        mModel1.mOriginalStart = 80;
1206        assertFalse(EditEventHelper.isFirstEventInSeries(mModel1, mModel2));
1207    }
1208
1209    @Smoke
1210    @SmallTest
1211    public void testAddRecurrenceRule() {
1212        mActivity = buildTestContext();
1213        mHelper = new EditEventHelper(mActivity, null);
1214        mValues = new ContentValues();
1215        mExpectedValues = new ContentValues();
1216        mModel1 = new CalendarEventModel();
1217
1218        mExpectedValues.put(Events.RRULE, "Weekly, Monday");
1219        mExpectedValues.put(Events.DURATION, "P60S");
1220        mExpectedValues.put(Events.DTEND, (Long) null);
1221
1222        mModel1.mRrule = "Weekly, Monday";
1223        mModel1.mStart = 1;
1224        mModel1.mEnd = 60001;
1225        mModel1.mAllDay = false;
1226
1227        mHelper.addRecurrenceRule(mValues, mModel1);
1228        assertEquals(mValues, mExpectedValues);
1229
1230        mExpectedValues.put(Events.DURATION, "P1D");
1231
1232        mModel1.mAllDay = true;
1233        mValues.clear();
1234
1235        mHelper.addRecurrenceRule(mValues, mModel1);
1236        assertEquals(mValues, mExpectedValues);
1237
1238    }
1239
1240    @Smoke
1241    @SmallTest
1242    public void testUpdateRecurrenceRule() {
1243        int selection = EditEventHelper.DOES_NOT_REPEAT;
1244        int weekStart = Calendar.SUNDAY;
1245        mModel1 = new CalendarEventModel();
1246        mModel1.mTimezone = Time.TIMEZONE_UTC;
1247        mModel1.mStart = 1272665741000L; // Fri, April 30th ~ 3:17PM
1248
1249        mModel1.mRrule = "This should go away";
1250
1251        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1252        assertNull(mModel1.mRrule);
1253
1254        mModel1.mRrule = "This shouldn't change";
1255        selection = EditEventHelper.REPEATS_CUSTOM;
1256
1257        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1258        assertEquals(mModel1.mRrule, "This shouldn't change");
1259
1260        selection = EditEventHelper.REPEATS_DAILY;
1261
1262        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1263        assertEquals(mModel1.mRrule, "FREQ=DAILY;WKST=SU");
1264
1265        selection = EditEventHelper.REPEATS_EVERY_WEEKDAY;
1266
1267        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1268        assertEquals(mModel1.mRrule, "FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR");
1269
1270        selection = EditEventHelper.REPEATS_WEEKLY_ON_DAY;
1271
1272        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1273        assertEquals(mModel1.mRrule, "FREQ=WEEKLY;WKST=SU;BYDAY=FR");
1274
1275        selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY;
1276
1277        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1278        assertEquals(mModel1.mRrule, "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=30");
1279
1280        selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT;
1281
1282        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1283        assertEquals(mModel1.mRrule, "FREQ=MONTHLY;WKST=SU;BYDAY=-1FR");
1284
1285        selection = EditEventHelper.REPEATS_YEARLY;
1286
1287        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
1288        assertEquals(mModel1.mRrule, "FREQ=YEARLY;WKST=SU");
1289    }
1290
1291    @Smoke
1292    @SmallTest
1293    public void testSetModelFromCursor() {
1294        mActivity = buildTestContext();
1295        mHelper = new EditEventHelper(mActivity, null);
1296        MatrixCursor c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
1297        c.addRow(TEST_CURSOR_DATA);
1298
1299        mModel1 = new CalendarEventModel();
1300        mModel2 = buildTestModel();
1301
1302        EditEventHelper.setModelFromCursor(mModel1, c);
1303        assertEquals(mModel1, mModel2);
1304
1305        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "0";
1306        c.close();
1307        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
1308        c.addRow(TEST_CURSOR_DATA);
1309
1310        mModel2.mAllDay = false;
1311        mModel2.mStart = TEST_START; // UTC time
1312
1313        EditEventHelper.setModelFromCursor(mModel1, c);
1314        assertEquals(mModel1, mModel2);
1315
1316        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_RRULE] = null;
1317        c.close();
1318        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
1319        c.addRow(TEST_CURSOR_DATA);
1320
1321        mModel2.mRrule = null;
1322        mModel2.mEnd = TEST_END;
1323        mModel2.mDuration = null;
1324
1325        EditEventHelper.setModelFromCursor(mModel1, c);
1326        assertEquals(mModel1, mModel2);
1327
1328        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "1";
1329        c.close();
1330        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
1331        c.addRow(TEST_CURSOR_DATA);
1332
1333        mModel2.mAllDay = true;
1334        mModel2.mStart = TEST_START; // Monday, May 3rd, midnight
1335        mModel2.mEnd = TEST_END; // Tuesday, May 4th, midnight
1336
1337        EditEventHelper.setModelFromCursor(mModel1, c);
1338        assertEquals(mModel1, mModel2);
1339    }
1340
1341    @Smoke
1342    @SmallTest
1343    public void testGetContentValuesFromModel() {
1344        mActivity = buildTestContext();
1345        mHelper = new EditEventHelper(mActivity, null);
1346        mExpectedValues = buildTestValues();
1347        mModel1 = buildTestModel();
1348
1349        ContentValues values = mHelper.getContentValuesFromModel(mModel1);
1350        assertEquals(values, mExpectedValues);
1351
1352        mModel1.mRrule = null;
1353        mModel1.mEnd = TEST_END;
1354
1355        mExpectedValues.put(Events.RRULE, (String) null);
1356        mExpectedValues.put(Events.DURATION, (String) null);
1357        mExpectedValues.put(Events.DTEND, TEST_END); // UTC time
1358
1359        values = mHelper.getContentValuesFromModel(mModel1);
1360        assertEquals(values, mExpectedValues);
1361
1362        mModel1.mAllDay = false;
1363
1364        mExpectedValues.put(Events.ALL_DAY, 0);
1365        mExpectedValues.put(Events.DTSTART, TEST_START);
1366        mExpectedValues.put(Events.DTEND, TEST_END);
1367        // not an allday event so timezone isn't modified
1368        mExpectedValues.put(Events.EVENT_TIMEZONE, "UTC");
1369
1370        values = mHelper.getContentValuesFromModel(mModel1);
1371        assertEquals(values, mExpectedValues);
1372    }
1373
1374    @Smoke
1375    @SmallTest
1376    public void testExtractDomain() {
1377        String domain = EditEventHelper.extractDomain("test.email@gmail.com");
1378        assertEquals(domain, "gmail.com");
1379
1380        domain = EditEventHelper.extractDomain("bademail.no#$%at symbol");
1381        assertNull(domain);
1382    }
1383
1384    private void addExpectedMinutes(ArrayList<ContentProviderOperation> expectedOps) {
1385        ContentProviderOperation.Builder b;
1386        mValues = new ContentValues();
1387
1388        mValues.clear();
1389        mValues.put(Reminders.MINUTES, 5);
1390        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1391        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
1392        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1393        expectedOps.add(b.build());
1394
1395        mValues.clear();
1396        mValues.put(Reminders.MINUTES, 10);
1397        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1398        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
1399        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1400        expectedOps.add(b.build());
1401
1402        mValues.clear();
1403        mValues.put(Reminders.MINUTES, 15);
1404        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1405        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
1406        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1407        expectedOps.add(b.build());
1408    }
1409
1410    private void addExpectedMinutesWithBackRef(ArrayList<ContentProviderOperation> expectedOps) {
1411        ContentProviderOperation.Builder b;
1412        mValues = new ContentValues();
1413
1414        mValues.clear();
1415        mValues.put(Reminders.MINUTES, 5);
1416        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1417        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1418        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
1419        expectedOps.add(b.build());
1420
1421        mValues.clear();
1422        mValues.put(Reminders.MINUTES, 10);
1423        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1424        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1425        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
1426        expectedOps.add(b.build());
1427
1428        mValues.clear();
1429        mValues.put(Reminders.MINUTES, 15);
1430        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1431        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
1432        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
1433        expectedOps.add(b.build());
1434    }
1435
1436    private static void assertEquals(ArrayList<ContentProviderOperation> expected,
1437            ArrayList<ContentProviderOperation> actual) {
1438        if (expected == null) {
1439            assertNull(actual);
1440        }
1441        int size = expected.size();
1442
1443        assertEquals(size, actual.size());
1444
1445        for (int i = 0; i < size; i++) {
1446            assertTrue(cpoEquals(expected.get(i), actual.get(i)));
1447        }
1448
1449    }
1450
1451    private static boolean cpoEquals(ContentProviderOperation cpo1, ContentProviderOperation cpo2) {
1452        if (cpo1 == null && cpo2 != null) {
1453            return false;
1454        }
1455        if (cpo1 == cpo2) {
1456            return true;
1457        }
1458        if (cpo2 == null) {
1459            return false;
1460        }
1461
1462        return TextUtils.equals(cpo1.toString(), cpo2.toString());
1463    }
1464
1465    // Generates a default model for testing. Should match up with
1466    // generateTestValues
1467    private CalendarEventModel buildTestModel() {
1468        CalendarEventModel model = new CalendarEventModel();
1469        model.mId = TEST_EVENT_ID;
1470        model.mTitle = "The Question";
1471        model.mDescription = "Evaluating Life, the Universe, and Everything";
1472        model.mLocation = "Earth Mk2";
1473        model.mAllDay = true;
1474        model.mHasAlarm = false;
1475        model.mCalendarId = 2;
1476        model.mStart = TEST_START; // Monday, May 3rd, local Time
1477        model.mDuration = "P3652421990D";
1478        // The model uses the local timezone for allday
1479        model.mTimezone = "UTC";
1480        model.mRrule = "FREQ=DAILY;WKST=SU";
1481        model.mSyncId = "unique per calendar stuff";
1482        model.mAvailability = false;
1483        model.mAccessLevel = 2; // This is one less than the values written if >0
1484        model.mOwnerAccount = "steve@gmail.com";
1485        model.mHasAttendeeData = true;
1486
1487
1488        return model;
1489    }
1490
1491    // Generates a default set of values for testing. Should match up with
1492    // generateTestModel
1493    private ContentValues buildTestValues() {
1494        ContentValues values = new ContentValues();
1495
1496        values.put(Events.CALENDAR_ID, 2L);
1497        values.put(Events.EVENT_TIMEZONE, "UTC"); // Allday events are converted
1498                                                  // to UTC for the db
1499        values.put(Events.TITLE, "The Question");
1500        values.put(Events.ALL_DAY, 1);
1501        values.put(Events.DTSTART, TEST_START); // Monday, May 3rd, midnight UTC time
1502        values.put(Events.HAS_ATTENDEE_DATA, 1);
1503
1504        values.put(Events.RRULE, "FREQ=DAILY;WKST=SU");
1505        values.put(Events.DURATION, "P3652421990D");
1506        values.put(Events.DTEND, (Long) null);
1507        values.put(Events.DESCRIPTION, "Evaluating Life, the Universe, and Everything");
1508        values.put(Events.EVENT_LOCATION, "Earth Mk2");
1509        values.put(Events.AVAILABILITY, 0);
1510        values.put(Events.ACCESS_LEVEL, 3); // This is one more than the model if
1511                                          // >0
1512
1513        return values;
1514    }
1515
1516    private ContentValues buildNonRecurringTestValues() {
1517        ContentValues values = buildTestValues();
1518        values.put(Events.DURATION, (String)null);
1519        values.put(Events.DTEND, TEST_END);
1520        values.put(Events.RRULE, (String)null);
1521        return values;
1522    }
1523
1524    // This gets called by EditEventHelper to read or write the data
1525    class TestProvider extends ContentProvider {
1526        int index = 0;
1527
1528        public TestProvider() {
1529        }
1530
1531        @Override
1532        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
1533                String orderBy) {
1534            return null;
1535        }
1536
1537        @Override
1538        public int delete(Uri uri, String selection, String[] selectionArgs) {
1539            return 0;
1540        }
1541
1542        @Override
1543        public String getType(Uri uri) {
1544            return null;
1545        }
1546
1547        @Override
1548        public boolean onCreate() {
1549            return false;
1550        }
1551
1552        @Override
1553        public Uri insert(Uri uri, ContentValues values) {
1554            // TODO Auto-generated method stub
1555            return null;
1556        }
1557
1558        @Override
1559        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
1560            // TODO Auto-generated method stub
1561            return 0;
1562        }
1563    }
1564
1565}
1566