blob: 165e6897e885995a2f95d4d06140256aa3769174 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts;
import static android.content.ContentProviderOperation.TYPE_ASSERT;
import static android.content.ContentProviderOperation.TYPE_DELETE;
import static android.content.ContentProviderOperation.TYPE_INSERT;
import static android.content.ContentProviderOperation.TYPE_UPDATE;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.ContactsContract.AggregationExceptions;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import com.android.contacts.RawContactModifierTests.MockContactsSource;
import com.android.contacts.model.RawContact;
import com.android.contacts.model.RawContactDelta;
import com.android.contacts.model.RawContactDelta.ValuesDelta;
import com.android.contacts.model.RawContactDeltaList;
import com.android.contacts.model.RawContactModifier;
import com.android.contacts.model.account.AccountType;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.util.ArrayList;
/**
* Tests for {@link RawContactDeltaList} which focus on "diff" operations that should
* create {@link AggregationExceptions} in certain cases.
*/
@LargeTest
public class RawContactDeltaListTests extends AndroidTestCase {
public static final String TAG = RawContactDeltaListTests.class.getSimpleName();
private static final long CONTACT_FIRST = 1;
private static final long CONTACT_SECOND = 2;
public static final long CONTACT_BOB = 10;
public static final long CONTACT_MARY = 11;
public static final long PHONE_RED = 20;
public static final long PHONE_GREEN = 21;
public static final long PHONE_BLUE = 22;
public static final long EMAIL_YELLOW = 25;
public static final long VER_FIRST = 100;
public static final long VER_SECOND = 200;
public static final String TEST_PHONE = "555-1212";
public static final String TEST_ACCOUNT = "org.example.test";
public RawContactDeltaListTests() {
super();
}
@Override
public void setUp() {
mContext = getContext();
}
/**
* Build a {@link AccountType} that has various odd constraints for
* testing purposes.
*/
protected AccountType getAccountType() {
return new MockContactsSource();
}
static ContentValues getValues(ContentProviderOperation operation)
throws NoSuchFieldException, IllegalAccessException {
final Field field = ContentProviderOperation.class.getDeclaredField("mValues");
field.setAccessible(true);
return (ContentValues) field.get(operation);
}
static RawContactDelta getUpdate(Context context, long rawContactId) {
final RawContact before = RawContactDeltaTests.getRawContact(context, rawContactId,
RawContactDeltaTests.TEST_PHONE_ID);
return RawContactDelta.fromBefore(before);
}
static RawContactDelta getInsert() {
final ContentValues after = new ContentValues();
after.put(RawContacts.ACCOUNT_NAME, RawContactDeltaTests.TEST_ACCOUNT_NAME);
after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
final ValuesDelta values = ValuesDelta.fromAfter(after);
return new RawContactDelta(values);
}
static RawContactDeltaList buildSet(RawContactDelta... deltas) {
final RawContactDeltaList set = RawContactDeltaList.fromSingle(deltas[0]);
for (int i = 1; i < deltas.length; i++) {
set.add(deltas[i]);
}
return set;
}
static RawContactDelta buildBeforeEntity(Context context, long rawContactId, long version,
ContentValues... entries) {
// Build an existing contact read from database
final ContentValues contact = new ContentValues();
contact.put(RawContacts.VERSION, version);
contact.put(RawContacts._ID, rawContactId);
final RawContact before = new RawContact(context, contact);
for (ContentValues entry : entries) {
before.addDataItemValues(entry);
}
return RawContactDelta.fromBefore(before);
}
static RawContactDelta buildAfterEntity(ContentValues... entries) {
// Build an existing contact read from database
final ContentValues contact = new ContentValues();
contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT);
final RawContactDelta after = new RawContactDelta(ValuesDelta.fromAfter(contact));
for (ContentValues entry : entries) {
after.addEntry(ValuesDelta.fromAfter(entry));
}
return after;
}
static ContentValues buildPhone(long phoneId) {
return buildPhone(phoneId, Long.toString(phoneId));
}
static ContentValues buildPhone(long phoneId, String value) {
final ContentValues values = new ContentValues();
values.put(Data._ID, phoneId);
values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
values.put(Phone.NUMBER, value);
values.put(Phone.TYPE, Phone.TYPE_HOME);
return values;
}
static ContentValues buildEmail(long emailId) {
final ContentValues values = new ContentValues();
values.put(Data._ID, emailId);
values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
values.put(Email.DATA, Long.toString(emailId));
values.put(Email.TYPE, Email.TYPE_HOME);
return values;
}
static void insertPhone(RawContactDeltaList set, long rawContactId, ContentValues values) {
final RawContactDelta match = set.getByRawContactId(rawContactId);
match.addEntry(ValuesDelta.fromAfter(values));
}
static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) {
final RawContactDelta match = set.getByRawContactId(rawContactId);
return match.getEntry(dataId);
}
static void assertDiffPattern(RawContactDelta delta, ContentProviderOperation... pattern) {
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
delta.buildAssert(diff);
delta.buildDiff(diff);
assertDiffPattern(diff, pattern);
}
static void assertDiffPattern(RawContactDeltaList set, ContentProviderOperation... pattern) {
assertDiffPattern(set.buildDiff(), pattern);
}
static void assertDiffPattern(ArrayList<ContentProviderOperation> diff,
ContentProviderOperation... pattern) {
assertEquals("Unexpected operations", pattern.length, diff.size());
for (int i = 0; i < pattern.length; i++) {
final ContentProviderOperation expected = pattern[i];
final ContentProviderOperation found = diff.get(i);
assertEquals("Unexpected uri", expected.getUri(), found.getUri());
final String expectedType = getStringForType(expected.getType());
final String foundType = getStringForType(found.getType());
assertEquals("Unexpected type", expectedType, foundType);
if (expected.getType() == TYPE_DELETE) continue;
try {
final ContentValues expectedValues = getValues(expected);
final ContentValues foundValues = getValues(found);
expectedValues.remove(BaseColumns._ID);
foundValues.remove(BaseColumns._ID);
assertEquals("Unexpected values", expectedValues, foundValues);
} catch (NoSuchFieldException e) {
fail(e.toString());
} catch (IllegalAccessException e) {
fail(e.toString());
}
}
}
static String getStringForType(int type) {
switch (type) {
case TYPE_ASSERT: return "TYPE_ASSERT";
case TYPE_INSERT: return "TYPE_INSERT";
case TYPE_UPDATE: return "TYPE_UPDATE";
case TYPE_DELETE: return "TYPE_DELETE";
default: return Integer.toString(type);
}
}
static ContentProviderOperation buildAssertVersion(long version) {
final ContentValues values = new ContentValues();
values.put(RawContacts.VERSION, version);
return buildOper(RawContacts.CONTENT_URI, TYPE_ASSERT, values);
}
static ContentProviderOperation buildAggregationModeUpdate(int mode) {
final ContentValues values = new ContentValues();
values.put(RawContacts.AGGREGATION_MODE, mode);
return buildOper(RawContacts.CONTENT_URI, TYPE_UPDATE, values);
}
static ContentProviderOperation buildUpdateAggregationSuspended() {
return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED);
}
static ContentProviderOperation buildUpdateAggregationDefault() {
return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT);
}
static ContentProviderOperation buildUpdateAggregationKeepTogether(long rawContactId) {
final ContentValues values = new ContentValues();
values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
return buildOper(AggregationExceptions.CONTENT_URI, TYPE_UPDATE, values);
}
static ContentValues buildDataInsert(ValuesDelta values, long rawContactId) {
final ContentValues insertValues = values.getCompleteValues();
insertValues.put(Data.RAW_CONTACT_ID, rawContactId);
return insertValues;
}
static ContentProviderOperation buildDelete(Uri uri) {
return buildOper(uri, TYPE_DELETE, (ContentValues)null);
}
static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) {
return buildOper(uri, type, values.getCompleteValues());
}
static ContentProviderOperation buildOper(Uri uri, int type, ContentValues values) {
switch (type) {
case TYPE_ASSERT:
return ContentProviderOperation.newAssertQuery(uri).withValues(values).build();
case TYPE_INSERT:
return ContentProviderOperation.newInsert(uri).withValues(values).build();
case TYPE_UPDATE:
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
case TYPE_DELETE:
return ContentProviderOperation.newDelete(uri).build();
}
return null;
}
static Long getVersion(RawContactDeltaList set, Long rawContactId) {
return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
}
/**
* Count number of {@link AggregationExceptions} updates contained in the
* given list of {@link ContentProviderOperation}.
*/
static int countExceptionUpdates(ArrayList<ContentProviderOperation> diff) {
int updateCount = 0;
for (ContentProviderOperation oper : diff) {
if (AggregationExceptions.CONTENT_URI.equals(oper.getUri())
&& oper.getType() == ContentProviderOperation.TYPE_UPDATE) {
updateCount++;
}
}
return updateCount;
}
public void testInsert() {
final RawContactDelta insert = getInsert();
final RawContactDeltaList set = buildSet(insert);
// Inserting single shouldn't create rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
final int exceptionCount = countExceptionUpdates(diff);
assertEquals("Unexpected exception updates", 0, exceptionCount);
}
public void testUpdateUpdate() {
final RawContactDelta updateFirst = getUpdate(mContext, CONTACT_FIRST);
final RawContactDelta updateSecond = getUpdate(mContext, CONTACT_SECOND);
final RawContactDeltaList set = buildSet(updateFirst, updateSecond);
// Updating two existing shouldn't create rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
final int exceptionCount = countExceptionUpdates(diff);
assertEquals("Unexpected exception updates", 0, exceptionCount);
}
public void testUpdateInsert() {
final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
final RawContactDelta insert = getInsert();
final RawContactDeltaList set = buildSet(update, insert);
// New insert should only create one rule
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
final int exceptionCount = countExceptionUpdates(diff);
assertEquals("Unexpected exception updates", 1, exceptionCount);
}
public void testInsertUpdateInsert() {
final RawContactDelta insertFirst = getInsert();
final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
final RawContactDelta insertSecond = getInsert();
final RawContactDeltaList set = buildSet(insertFirst, update, insertSecond);
// Two inserts should create two rules to bind against single existing
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
final int exceptionCount = countExceptionUpdates(diff);
assertEquals("Unexpected exception updates", 2, exceptionCount);
}
public void testInsertInsertInsert() {
final RawContactDelta insertFirst = getInsert();
final RawContactDelta insertSecond = getInsert();
final RawContactDelta insertThird = getInsert();
final RawContactDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
// Three new inserts should create only two binding rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
final int exceptionCount = countExceptionUpdates(diff);
assertEquals("Unexpected exception updates", 2, exceptionCount);
}
public void testMergeDataRemoteInsert() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Merge in second version, verify they match
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertEquals("Unexpected change when merging", second, merged);
}
public void testMergeDataLocalUpdateRemoteInsert() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Change the local number to trigger update
final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
phone.put(Phone.NUMBER, TEST_PHONE);
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
buildUpdateAggregationDefault());
// Merge in the second version, verify diff matches
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
buildUpdateAggregationDefault());
}
public void testMergeDataLocalUpdateRemoteDelete() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_GREEN)));
// Change the local number to trigger update
final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
phone.put(Phone.NUMBER, TEST_PHONE);
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
buildUpdateAggregationDefault());
// Merge in the second version, verify that our update changed to
// insert, since RED was deleted on remote side
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)),
buildUpdateAggregationDefault());
}
public void testMergeDataLocalDeleteRemoteUpdate() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED, TEST_PHONE)));
// Delete phone locally
final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
phone.markDeleted();
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildDelete(Data.CONTENT_URI),
buildUpdateAggregationDefault());
// Merge in the second version, verify that our delete remains
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
buildDelete(Data.CONTENT_URI),
buildUpdateAggregationDefault());
}
public void testMergeDataLocalInsertRemoteInsert() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Insert new phone locally
final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE));
first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone);
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
buildUpdateAggregationDefault());
// Merge in the second version, verify that our insert remains
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
buildUpdateAggregationDefault());
}
public void testMergeRawContactLocalInsertRemoteInsert() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED)), buildBeforeEntity(mContext, CONTACT_MARY,
VER_SECOND, buildPhone(PHONE_RED)));
// Add new contact locally, should remain insert
final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE);
final RawContactDelta joeContact = buildAfterEntity(joePhoneInsert);
final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues();
joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
first.add(joeContact);
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
buildUpdateAggregationKeepTogether(CONTACT_BOB));
// Merge in the second version, verify that our insert remains
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildAssertVersion(VER_SECOND),
buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
buildUpdateAggregationKeepTogether(CONTACT_BOB));
}
public void testMergeRawContactLocalDeleteRemoteDelete() {
final RawContactDeltaList first = buildSet(
buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(
buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
// Remove contact locally
first.getByRawContactId(CONTACT_MARY).markDeleted();
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildAssertVersion(VER_FIRST),
buildDelete(RawContacts.CONTENT_URI));
// Merge in the second version, verify that our delete isn't needed
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged);
}
public void testMergeRawContactLocalUpdateRemoteDelete() {
final RawContactDeltaList first = buildSet(
buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(
buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
// Perform local update
final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED);
phone.put(Phone.NUMBER, TEST_PHONE);
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
buildUpdateAggregationDefault());
final ContentValues phoneInsert = phone.getCompleteValues();
final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues()
.getCompleteValues();
contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
// Merge and verify that update turned into insert
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert),
buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
buildUpdateAggregationKeepTogether(CONTACT_BOB));
}
public void testMergeUsesNewVersion() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildPhone(PHONE_RED)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildPhone(PHONE_RED)));
assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB));
assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB));
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
}
public void testMergeAfterEnsureAndTrim() {
final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_FIRST, buildEmail(EMAIL_YELLOW)));
final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
VER_SECOND, buildEmail(EMAIL_YELLOW)));
// Ensure we have at least one phone
final AccountType source = getAccountType();
final RawContactDelta bobContact = first.getByRawContactId(CONTACT_BOB);
RawContactModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true);
// Make sure the update would insert a row
assertDiffPattern(first,
buildAssertVersion(VER_FIRST),
buildUpdateAggregationSuspended(),
buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)),
buildUpdateAggregationDefault());
// Trim values and ensure that we don't insert things
RawContactModifier.trimEmpty(bobContact, source);
assertDiffPattern(first);
// Now re-parent the change, which should remain no-op
final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
assertDiffPattern(merged);
}
}