/* * 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 com.android.contacts.model.EntityDelta; import com.android.contacts.model.EntityDelta.ValuesDelta; import com.google.android.collect.Lists; import static android.content.ContentProviderOperation.TYPE_INSERT; import static android.content.ContentProviderOperation.TYPE_UPDATE; import static android.content.ContentProviderOperation.TYPE_DELETE; import static android.content.ContentProviderOperation.TYPE_ASSERT; import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Entity; import android.content.ContentProviderOperation.Builder; import android.os.Parcel; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import java.util.ArrayList; /** * Tests for {@link EntityDelta} and {@link ValuesDelta}. These tests * focus on passing changes across {@link Parcel}, and verifying that they * correctly build expected "diff" operations. */ @LargeTest public class EntityDeltaTests extends AndroidTestCase { public static final String TAG = "EntityDeltaTests"; public static final long TEST_CONTACT_ID = 12; public static final long TEST_PHONE_ID = 24; public static final String TEST_PHONE_NUMBER_1 = "218-555-1111"; public static final String TEST_PHONE_NUMBER_2 = "218-555-2222"; public static final String TEST_ACCOUNT_NAME = "TEST"; public EntityDeltaTests() { super(); } @Override public void setUp() { mContext = getContext(); } public static Entity getEntity(long contactId, long phoneId) { // Build an existing contact read from database final ContentValues contact = new ContentValues(); contact.put(RawContacts.VERSION, 43); contact.put(RawContacts._ID, contactId); final ContentValues phone = new ContentValues(); phone.put(Data._ID, phoneId); phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); phone.put(Phone.TYPE, Phone.TYPE_HOME); final Entity before = new Entity(contact); before.addSubValue(Data.CONTENT_URI, phone); return before; } /** * Test that {@link EntityDelta#mergeAfter(EntityDelta)} correctly passes * any changes through the {@link Parcel} object. This enforces that * {@link EntityDelta} should be identical when serialized against the same * "before" {@link Entity}. */ public void testParcelChangesNone() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); final EntityDelta dest = EntityDelta.fromBefore(before); // Merge modified values and assert they match final EntityDelta merged = EntityDelta.mergeAfter(dest, source); assertEquals("Unexpected change when merging", source, merged); } public void testParcelChangesInsert() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); final EntityDelta dest = EntityDelta.fromBefore(before); // Add a new row and pass across parcel, should be same final ContentValues phone = new ContentValues(); phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); phone.put(Phone.TYPE, Phone.TYPE_WORK); source.addEntry(ValuesDelta.fromAfter(phone)); // Merge modified values and assert they match final EntityDelta merged = EntityDelta.mergeAfter(dest, source); assertEquals("Unexpected change when merging", source, merged); } public void testParcelChangesUpdate() { // Update existing row and pass across parcel, should be same final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); final EntityDelta dest = EntityDelta.fromBefore(before); final ValuesDelta child = source.getEntry(TEST_PHONE_ID); child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); // Merge modified values and assert they match final EntityDelta merged = EntityDelta.mergeAfter(dest, source); assertEquals("Unexpected change when merging", source, merged); } public void testParcelChangesDelete() { // Delete a row and pass across parcel, should be same final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); final EntityDelta dest = EntityDelta.fromBefore(before); final ValuesDelta child = source.getEntry(TEST_PHONE_ID); child.markDeleted(); // Merge modified values and assert they match final EntityDelta merged = EntityDelta.mergeAfter(dest, source); assertEquals("Unexpected change when merging", source, merged); } /** * Test that {@link ValuesDelta#buildDiff(android.net.Uri)} is correctly * built for insert, update, and delete cases. Note this only tests behavior * for individual {@link Data} rows. */ public void testValuesDiffNone() { final ContentValues before = new ContentValues(); before.put(Data._ID, TEST_PHONE_ID); before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); final ValuesDelta values = ValuesDelta.fromBefore(before); // None action shouldn't produce a builder final Builder builder = values.buildDiff(Data.CONTENT_URI); assertNull("None action produced a builder", builder); } public void testValuesDiffInsert() { final ContentValues after = new ContentValues(); after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); final ValuesDelta values = ValuesDelta.fromAfter(after); // Should produce an insert action final Builder builder = values.buildDiff(Data.CONTENT_URI); final int type = builder.build().getType(); assertEquals("Didn't produce insert action", TYPE_INSERT, type); } public void testValuesDiffUpdate() { final ContentValues before = new ContentValues(); before.put(Data._ID, TEST_PHONE_ID); before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); final ValuesDelta values = ValuesDelta.fromBefore(before); values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); // Should produce an update action final Builder builder = values.buildDiff(Data.CONTENT_URI); final int type = builder.build().getType(); assertEquals("Didn't produce update action", TYPE_UPDATE, type); } public void testValuesDiffDelete() { final ContentValues before = new ContentValues(); before.put(Data._ID, TEST_PHONE_ID); before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); final ValuesDelta values = ValuesDelta.fromBefore(before); values.markDeleted(); // Should produce a delete action final Builder builder = values.buildDiff(Data.CONTENT_URI); final int type = builder.build().getType(); assertEquals("Didn't produce delete action", TYPE_DELETE, type); } /** * Test that {@link EntityDelta#buildDiff(ArrayList)} is correctly built for * insert, update, and delete cases. This only tests a subset of possible * {@link Data} row changes. */ public void testEntityDiffNone() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); // Assert that writing unchanged produces few operations final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildDiff(diff); assertTrue("Created changes when none needed", (diff.size() == 0)); } public void testEntityDiffNoneInsert() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); // Insert a new phone number final ContentValues phone = new ContentValues(); phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); phone.put(Phone.TYPE, Phone.TYPE_WORK); source.addEntry(ValuesDelta.fromAfter(phone)); // Assert two operations: insert Data row and enforce version final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 4, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); } { final ContentProviderOperation oper = diff.get(1); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(2); assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(3); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } } public void testEntityDiffUpdateInsert() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); // Update parent contact values source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED); // Insert a new phone number final ContentValues phone = new ContentValues(); phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); phone.put(Phone.TYPE, Phone.TYPE_WORK); source.addEntry(ValuesDelta.fromAfter(phone)); // Assert three operations: update Contact, insert Data row, enforce version final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 5, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); } { final ContentProviderOperation oper = diff.get(1); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(2); assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(3); assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(4); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } } public void testEntityDiffNoneUpdate() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); // Update existing phone number final ValuesDelta child = source.getEntry(TEST_PHONE_ID); child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); // Assert that version is enforced final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 4, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); } { final ContentProviderOperation oper = diff.get(1); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(2); assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(3); assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } } public void testEntityDiffDelete() { final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); final EntityDelta source = EntityDelta.fromBefore(before); // Delete entire entity source.getValues().markDeleted(); // Assert two operations: delete Contact and enforce version final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 2, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); } { final ContentProviderOperation oper = diff.get(1); assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } } public void testEntityDiffInsert() { // Insert a RawContact final ContentValues after = new ContentValues(); after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); after.put(RawContacts.SEND_TO_VOICEMAIL, 1); final ValuesDelta values = ValuesDelta.fromAfter(after); final EntityDelta source = new EntityDelta(values); // Assert two operations: delete Contact and enforce version final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 2, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } } public void testEntityDiffInsertInsert() { // Insert a RawContact final ContentValues after = new ContentValues(); after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); after.put(RawContacts.SEND_TO_VOICEMAIL, 1); final ValuesDelta values = ValuesDelta.fromAfter(after); final EntityDelta source = new EntityDelta(values); // Insert a new phone number final ContentValues phone = new ContentValues(); phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); phone.put(Phone.TYPE, Phone.TYPE_WORK); source.addEntry(ValuesDelta.fromAfter(phone)); // Assert two operations: delete Contact and enforce version final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); source.buildAssert(diff); source.buildDiff(diff); assertEquals("Unexpected operations", 3, diff.size()); { final ContentProviderOperation oper = diff.get(0); assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); } { final ContentProviderOperation oper = diff.get(1); assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); } } }