EntityDeltaListTests.java revision 4597c92d655d45447780b32c7572acef110b6ed1
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.contacts;
18
19import static android.content.ContentProviderOperation.TYPE_ASSERT;
20import static android.content.ContentProviderOperation.TYPE_DELETE;
21import static android.content.ContentProviderOperation.TYPE_INSERT;
22import static android.content.ContentProviderOperation.TYPE_UPDATE;
23
24import com.android.contacts.EntityModifierTests.MockContactsSource;
25import com.android.contacts.model.AccountType;
26import com.android.contacts.model.EntityDelta;
27import com.android.contacts.model.EntityModifier;
28import com.android.contacts.model.EntityDeltaList;
29import com.android.contacts.model.EntityDelta.ValuesDelta;
30import com.google.android.collect.Lists;
31
32import android.content.ContentProviderOperation;
33import android.content.ContentValues;
34import android.content.Entity;
35import android.net.Uri;
36import android.provider.BaseColumns;
37import android.provider.ContactsContract.AggregationExceptions;
38import android.provider.ContactsContract.Data;
39import android.provider.ContactsContract.RawContacts;
40import android.provider.ContactsContract.CommonDataKinds.Email;
41import android.provider.ContactsContract.CommonDataKinds.Phone;
42import android.test.AndroidTestCase;
43import android.test.suitebuilder.annotation.LargeTest;
44
45import java.lang.reflect.Field;
46import java.util.ArrayList;
47
48/**
49 * Tests for {@link EntityDeltaList} which focus on "diff" operations that should
50 * create {@link AggregationExceptions} in certain cases.
51 */
52@LargeTest
53public class EntityDeltaListTests extends AndroidTestCase {
54    public static final String TAG = "EntityDeltaListTests";
55
56    private static final long CONTACT_FIRST = 1;
57    private static final long CONTACT_SECOND = 2;
58
59    public static final long CONTACT_BOB = 10;
60    public static final long CONTACT_MARY = 11;
61
62    public static final long PHONE_RED = 20;
63    public static final long PHONE_GREEN = 21;
64    public static final long PHONE_BLUE = 22;
65
66    public static final long EMAIL_YELLOW = 25;
67
68    public static final long VER_FIRST = 100;
69    public static final long VER_SECOND = 200;
70
71    public static final String TEST_PHONE = "555-1212";
72    public static final String TEST_ACCOUNT = "org.example.test";
73
74    public EntityDeltaListTests() {
75        super();
76    }
77
78    @Override
79    public void setUp() {
80        mContext = getContext();
81    }
82
83    /**
84     * Build a {@link AccountType} that has various odd constraints for
85     * testing purposes.
86     */
87    protected AccountType getSource() {
88        final AccountType source = new MockContactsSource();
89        source.ensureInflated(getContext(), AccountType.LEVEL_CONSTRAINTS);
90        return source;
91    }
92
93    static ContentValues getValues(ContentProviderOperation operation)
94            throws NoSuchFieldException, IllegalAccessException {
95        final Field field = ContentProviderOperation.class.getDeclaredField("mValues");
96        field.setAccessible(true);
97        return (ContentValues) field.get(operation);
98    }
99
100    static EntityDelta getUpdate(long rawContactId) {
101        final Entity before = EntityDeltaTests.getEntity(rawContactId,
102                EntityDeltaTests.TEST_PHONE_ID);
103        return EntityDelta.fromBefore(before);
104    }
105
106    static EntityDelta getInsert() {
107        final ContentValues after = new ContentValues();
108        after.put(RawContacts.ACCOUNT_NAME, EntityDeltaTests.TEST_ACCOUNT_NAME);
109        after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
110
111        final ValuesDelta values = ValuesDelta.fromAfter(after);
112        return new EntityDelta(values);
113    }
114
115    static EntityDeltaList buildSet(EntityDelta... deltas) {
116        final EntityDeltaList set = EntityDeltaList.fromSingle(deltas[0]);
117        for (int i = 1; i < deltas.length; i++) {
118            set.add(deltas[i]);
119        }
120        return set;
121    }
122
123    static EntityDelta buildBeforeEntity(long rawContactId, long version,
124            ContentValues... entries) {
125        // Build an existing contact read from database
126        final ContentValues contact = new ContentValues();
127        contact.put(RawContacts.VERSION, version);
128        contact.put(RawContacts._ID, rawContactId);
129        final Entity before = new Entity(contact);
130        for (ContentValues entry : entries) {
131            before.addSubValue(Data.CONTENT_URI, entry);
132        }
133        return EntityDelta.fromBefore(before);
134    }
135
136    static EntityDelta buildAfterEntity(ContentValues... entries) {
137        // Build an existing contact read from database
138        final ContentValues contact = new ContentValues();
139        contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT);
140        final EntityDelta after = new EntityDelta(ValuesDelta.fromAfter(contact));
141        for (ContentValues entry : entries) {
142            after.addEntry(ValuesDelta.fromAfter(entry));
143        }
144        return after;
145    }
146
147    static ContentValues buildPhone(long phoneId) {
148        return buildPhone(phoneId, Long.toString(phoneId));
149    }
150
151    static ContentValues buildPhone(long phoneId, String value) {
152        final ContentValues values = new ContentValues();
153        values.put(Data._ID, phoneId);
154        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
155        values.put(Phone.NUMBER, value);
156        values.put(Phone.TYPE, Phone.TYPE_HOME);
157        return values;
158    }
159
160    static ContentValues buildEmail(long emailId) {
161        final ContentValues values = new ContentValues();
162        values.put(Data._ID, emailId);
163        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
164        values.put(Email.DATA, Long.toString(emailId));
165        values.put(Email.TYPE, Email.TYPE_HOME);
166        return values;
167    }
168
169    static void insertPhone(EntityDeltaList set, long rawContactId, ContentValues values) {
170        final EntityDelta match = set.getByRawContactId(rawContactId);
171        match.addEntry(ValuesDelta.fromAfter(values));
172    }
173
174    static ValuesDelta getPhone(EntityDeltaList set, long rawContactId, long dataId) {
175        final EntityDelta match = set.getByRawContactId(rawContactId);
176        return match.getEntry(dataId);
177    }
178
179    static void assertDiffPattern(EntityDelta delta, ContentProviderOperation... pattern) {
180        final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
181        delta.buildAssert(diff);
182        delta.buildDiff(diff);
183        assertDiffPattern(diff, pattern);
184    }
185
186    static void assertDiffPattern(EntityDeltaList set, ContentProviderOperation... pattern) {
187        assertDiffPattern(set.buildDiff(), pattern);
188    }
189
190    static void assertDiffPattern(ArrayList<ContentProviderOperation> diff,
191            ContentProviderOperation... pattern) {
192        assertEquals("Unexpected operations", pattern.length, diff.size());
193        for (int i = 0; i < pattern.length; i++) {
194            final ContentProviderOperation expected = pattern[i];
195            final ContentProviderOperation found = diff.get(i);
196
197            assertEquals("Unexpected uri", expected.getUri(), found.getUri());
198
199            final String expectedType = getStringForType(expected.getType());
200            final String foundType = getStringForType(found.getType());
201            assertEquals("Unexpected type", expectedType, foundType);
202
203            if (expected.getType() == TYPE_DELETE) continue;
204
205            try {
206                final ContentValues expectedValues = getValues(expected);
207                final ContentValues foundValues = getValues(found);
208
209                expectedValues.remove(BaseColumns._ID);
210                foundValues.remove(BaseColumns._ID);
211
212                assertEquals("Unexpected values", expectedValues, foundValues);
213            } catch (NoSuchFieldException e) {
214                fail(e.toString());
215            } catch (IllegalAccessException e) {
216                fail(e.toString());
217            }
218        }
219    }
220
221    static String getStringForType(int type) {
222        switch (type) {
223            case TYPE_ASSERT: return "TYPE_ASSERT";
224            case TYPE_INSERT: return "TYPE_INSERT";
225            case TYPE_UPDATE: return "TYPE_UPDATE";
226            case TYPE_DELETE: return "TYPE_DELETE";
227            default: return Integer.toString(type);
228        }
229    }
230
231    static ContentProviderOperation buildAssertVersion(long version) {
232        final ContentValues values = new ContentValues();
233        values.put(RawContacts.VERSION, version);
234        return buildOper(RawContacts.CONTENT_URI, TYPE_ASSERT, values);
235    }
236
237    static ContentProviderOperation buildAggregationModeUpdate(int mode) {
238        final ContentValues values = new ContentValues();
239        values.put(RawContacts.AGGREGATION_MODE, mode);
240        return buildOper(RawContacts.CONTENT_URI, TYPE_UPDATE, values);
241    }
242
243    static ContentProviderOperation buildUpdateAggregationSuspended() {
244        return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED);
245    }
246
247    static ContentProviderOperation buildUpdateAggregationDefault() {
248        return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT);
249    }
250
251    static ContentProviderOperation buildUpdateAggregationKeepTogether(long rawContactId) {
252        final ContentValues values = new ContentValues();
253        values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
254        values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
255        return buildOper(AggregationExceptions.CONTENT_URI, TYPE_UPDATE, values);
256    }
257
258    static ContentValues buildDataInsert(ValuesDelta values, long rawContactId) {
259        final ContentValues insertValues = values.getCompleteValues();
260        insertValues.put(Data.RAW_CONTACT_ID, rawContactId);
261        return insertValues;
262    }
263
264    static ContentProviderOperation buildDelete(Uri uri) {
265        return buildOper(uri, TYPE_DELETE, (ContentValues)null);
266    }
267
268    static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) {
269        return buildOper(uri, type, values.getCompleteValues());
270    }
271
272    static ContentProviderOperation buildOper(Uri uri, int type, ContentValues values) {
273        switch (type) {
274            case TYPE_ASSERT:
275                return ContentProviderOperation.newAssertQuery(uri).withValues(values).build();
276            case TYPE_INSERT:
277                return ContentProviderOperation.newInsert(uri).withValues(values).build();
278            case TYPE_UPDATE:
279                return ContentProviderOperation.newUpdate(uri).withValues(values).build();
280            case TYPE_DELETE:
281                return ContentProviderOperation.newDelete(uri).build();
282        }
283        return null;
284    }
285
286    static Long getVersion(EntityDeltaList set, Long rawContactId) {
287        return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
288    }
289
290    /**
291     * Count number of {@link AggregationExceptions} updates contained in the
292     * given list of {@link ContentProviderOperation}.
293     */
294    static int countExceptionUpdates(ArrayList<ContentProviderOperation> diff) {
295        int updateCount = 0;
296        for (ContentProviderOperation oper : diff) {
297            if (AggregationExceptions.CONTENT_URI.equals(oper.getUri())
298                    && oper.getType() == ContentProviderOperation.TYPE_UPDATE) {
299                updateCount++;
300            }
301        }
302        return updateCount;
303    }
304
305    public void testInsert() {
306        final EntityDelta insert = getInsert();
307        final EntityDeltaList set = buildSet(insert);
308
309        // Inserting single shouldn't create rules
310        final ArrayList<ContentProviderOperation> diff = set.buildDiff();
311        final int exceptionCount = countExceptionUpdates(diff);
312        assertEquals("Unexpected exception updates", 0, exceptionCount);
313    }
314
315    public void testUpdateUpdate() {
316        final EntityDelta updateFirst = getUpdate(CONTACT_FIRST);
317        final EntityDelta updateSecond = getUpdate(CONTACT_SECOND);
318        final EntityDeltaList set = buildSet(updateFirst, updateSecond);
319
320        // Updating two existing shouldn't create rules
321        final ArrayList<ContentProviderOperation> diff = set.buildDiff();
322        final int exceptionCount = countExceptionUpdates(diff);
323        assertEquals("Unexpected exception updates", 0, exceptionCount);
324    }
325
326    public void testUpdateInsert() {
327        final EntityDelta update = getUpdate(CONTACT_FIRST);
328        final EntityDelta insert = getInsert();
329        final EntityDeltaList set = buildSet(update, insert);
330
331        // New insert should only create one rule
332        final ArrayList<ContentProviderOperation> diff = set.buildDiff();
333        final int exceptionCount = countExceptionUpdates(diff);
334        assertEquals("Unexpected exception updates", 1, exceptionCount);
335    }
336
337    public void testInsertUpdateInsert() {
338        final EntityDelta insertFirst = getInsert();
339        final EntityDelta update = getUpdate(CONTACT_FIRST);
340        final EntityDelta insertSecond = getInsert();
341        final EntityDeltaList set = buildSet(insertFirst, update, insertSecond);
342
343        // Two inserts should create two rules to bind against single existing
344        final ArrayList<ContentProviderOperation> diff = set.buildDiff();
345        final int exceptionCount = countExceptionUpdates(diff);
346        assertEquals("Unexpected exception updates", 2, exceptionCount);
347    }
348
349    public void testInsertInsertInsert() {
350        final EntityDelta insertFirst = getInsert();
351        final EntityDelta insertSecond = getInsert();
352        final EntityDelta insertThird = getInsert();
353        final EntityDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
354
355        // Three new inserts should create only two binding rules
356        final ArrayList<ContentProviderOperation> diff = set.buildDiff();
357        final int exceptionCount = countExceptionUpdates(diff);
358        assertEquals("Unexpected exception updates", 2, exceptionCount);
359    }
360
361    public void testMergeDataRemoteInsert() {
362        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
363                buildPhone(PHONE_RED)));
364        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
365                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
366
367        // Merge in second version, verify they match
368        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
369        assertEquals("Unexpected change when merging", second, merged);
370    }
371
372    public void testMergeDataLocalUpdateRemoteInsert() {
373        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
374                buildPhone(PHONE_RED)));
375        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
376                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
377
378        // Change the local number to trigger update
379        final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
380        phone.put(Phone.NUMBER, TEST_PHONE);
381
382        assertDiffPattern(first,
383                buildAssertVersion(VER_FIRST),
384                buildUpdateAggregationSuspended(),
385                buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
386                buildUpdateAggregationDefault());
387
388        // Merge in the second version, verify diff matches
389        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
390        assertDiffPattern(merged,
391                buildAssertVersion(VER_SECOND),
392                buildUpdateAggregationSuspended(),
393                buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
394                buildUpdateAggregationDefault());
395    }
396
397    public void testMergeDataLocalUpdateRemoteDelete() {
398        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
399                buildPhone(PHONE_RED)));
400        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
401                buildPhone(PHONE_GREEN)));
402
403        // Change the local number to trigger update
404        final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
405        phone.put(Phone.NUMBER, TEST_PHONE);
406
407        assertDiffPattern(first,
408                buildAssertVersion(VER_FIRST),
409                buildUpdateAggregationSuspended(),
410                buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
411                buildUpdateAggregationDefault());
412
413        // Merge in the second version, verify that our update changed to
414        // insert, since RED was deleted on remote side
415        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
416        assertDiffPattern(merged,
417                buildAssertVersion(VER_SECOND),
418                buildUpdateAggregationSuspended(),
419                buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)),
420                buildUpdateAggregationDefault());
421    }
422
423    public void testMergeDataLocalDeleteRemoteUpdate() {
424        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
425                buildPhone(PHONE_RED)));
426        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
427                buildPhone(PHONE_RED, TEST_PHONE)));
428
429        // Delete phone locally
430        final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
431        phone.markDeleted();
432
433        assertDiffPattern(first,
434                buildAssertVersion(VER_FIRST),
435                buildUpdateAggregationSuspended(),
436                buildDelete(Data.CONTENT_URI),
437                buildUpdateAggregationDefault());
438
439        // Merge in the second version, verify that our delete remains
440        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
441        assertDiffPattern(merged,
442                buildAssertVersion(VER_SECOND),
443                buildUpdateAggregationSuspended(),
444                buildDelete(Data.CONTENT_URI),
445                buildUpdateAggregationDefault());
446    }
447
448    public void testMergeDataLocalInsertRemoteInsert() {
449        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
450                buildPhone(PHONE_RED)));
451        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
452                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
453
454        // Insert new phone locally
455        final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE));
456        first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone);
457        assertDiffPattern(first,
458                buildAssertVersion(VER_FIRST),
459                buildUpdateAggregationSuspended(),
460                buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
461                buildUpdateAggregationDefault());
462
463        // Merge in the second version, verify that our insert remains
464        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
465        assertDiffPattern(merged,
466                buildAssertVersion(VER_SECOND),
467                buildUpdateAggregationSuspended(),
468                buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
469                buildUpdateAggregationDefault());
470    }
471
472    public void testMergeRawContactLocalInsertRemoteInsert() {
473        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
474                buildPhone(PHONE_RED)));
475        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
476                buildPhone(PHONE_RED)), buildBeforeEntity(CONTACT_MARY, VER_SECOND,
477                buildPhone(PHONE_RED)));
478
479        // Add new contact locally, should remain insert
480        final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE);
481        final EntityDelta joeContact = buildAfterEntity(joePhoneInsert);
482        final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues();
483        joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
484        first.add(joeContact);
485        assertDiffPattern(first,
486                buildAssertVersion(VER_FIRST),
487                buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
488                buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
489                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
490                buildUpdateAggregationKeepTogether(CONTACT_BOB));
491
492        // Merge in the second version, verify that our insert remains
493        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
494        assertDiffPattern(merged,
495                buildAssertVersion(VER_SECOND),
496                buildAssertVersion(VER_SECOND),
497                buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
498                buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
499                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
500                buildUpdateAggregationKeepTogether(CONTACT_BOB));
501    }
502
503    public void testMergeRawContactLocalDeleteRemoteDelete() {
504        final EntityDeltaList first = buildSet(
505                buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
506                buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
507        final EntityDeltaList second = buildSet(
508                buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
509
510        // Remove contact locally
511        first.getByRawContactId(CONTACT_MARY).markDeleted();
512        assertDiffPattern(first,
513                buildAssertVersion(VER_FIRST),
514                buildAssertVersion(VER_FIRST),
515                buildDelete(RawContacts.CONTENT_URI));
516
517        // Merge in the second version, verify that our delete isn't needed
518        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
519        assertDiffPattern(merged);
520    }
521
522    public void testMergeRawContactLocalUpdateRemoteDelete() {
523        final EntityDeltaList first = buildSet(
524                buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
525                buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
526        final EntityDeltaList second = buildSet(
527                buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
528
529        // Perform local update
530        final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED);
531        phone.put(Phone.NUMBER, TEST_PHONE);
532        assertDiffPattern(first,
533                buildAssertVersion(VER_FIRST),
534                buildAssertVersion(VER_FIRST),
535                buildUpdateAggregationSuspended(),
536                buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
537                buildUpdateAggregationDefault());
538
539        final ContentValues phoneInsert = phone.getCompleteValues();
540        final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues()
541                .getCompleteValues();
542        contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
543
544        // Merge and verify that update turned into insert
545        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
546        assertDiffPattern(merged,
547                buildAssertVersion(VER_SECOND),
548                buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
549                buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert),
550                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
551                buildUpdateAggregationKeepTogether(CONTACT_BOB));
552    }
553
554    public void testMergeUsesNewVersion() {
555        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
556                buildPhone(PHONE_RED)));
557        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
558                buildPhone(PHONE_RED)));
559
560        assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB));
561        assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB));
562
563        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
564        assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
565    }
566
567    public void testMergeAfterEnsureAndTrim() {
568        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
569                buildEmail(EMAIL_YELLOW)));
570        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
571                buildEmail(EMAIL_YELLOW)));
572
573        // Ensure we have at least one phone
574        final AccountType source = getSource();
575        final EntityDelta bobContact = first.getByRawContactId(CONTACT_BOB);
576        EntityModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
577        final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true);
578
579        // Make sure the update would insert a row
580        assertDiffPattern(first,
581                buildAssertVersion(VER_FIRST),
582                buildUpdateAggregationSuspended(),
583                buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)),
584                buildUpdateAggregationDefault());
585
586        // Trim values and ensure that we don't insert things
587        EntityModifier.trimEmpty(bobContact, source);
588        assertDiffPattern(first);
589
590        // Now re-parent the change, which should remain no-op
591        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
592        assertDiffPattern(merged);
593    }
594}
595