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 17 package com.android.contacts; 18 19 import com.android.contacts.model.EntityDelta; 20 import com.android.contacts.model.EntityDelta.ValuesDelta; 21 import com.google.android.collect.Lists; 22 23 import static android.content.ContentProviderOperation.TYPE_INSERT; 24 import static android.content.ContentProviderOperation.TYPE_UPDATE; 25 import static android.content.ContentProviderOperation.TYPE_DELETE; 26 import static android.content.ContentProviderOperation.TYPE_ASSERT; 27 28 import android.content.ContentProviderOperation; 29 import android.content.ContentValues; 30 import android.content.Entity; 31 import android.content.ContentProviderOperation.Builder; 32 import android.os.Parcel; 33 import android.provider.ContactsContract.Data; 34 import android.provider.ContactsContract.RawContacts; 35 import android.provider.ContactsContract.CommonDataKinds.Phone; 36 import android.test.AndroidTestCase; 37 import android.test.suitebuilder.annotation.LargeTest; 38 39 import java.util.ArrayList; 40 41 /** 42 * Tests for {@link EntityDelta} and {@link ValuesDelta}. These tests 43 * focus on passing changes across {@link Parcel}, and verifying that they 44 * correctly build expected "diff" operations. 45 */ 46 @LargeTest 47 public class EntityDeltaTests extends AndroidTestCase { 48 public static final String TAG = "EntityDeltaTests"; 49 50 public static final long TEST_CONTACT_ID = 12; 51 public static final long TEST_PHONE_ID = 24; 52 53 public static final String TEST_PHONE_NUMBER_1 = "218-555-1111"; 54 public static final String TEST_PHONE_NUMBER_2 = "218-555-2222"; 55 56 public static final String TEST_ACCOUNT_NAME = "TEST"; 57 58 public EntityDeltaTests() { 59 super(); 60 } 61 62 @Override 63 public void setUp() { 64 mContext = getContext(); 65 } 66 67 public static Entity getEntity(long contactId, long phoneId) { 68 // Build an existing contact read from database 69 final ContentValues contact = new ContentValues(); 70 contact.put(RawContacts.VERSION, 43); 71 contact.put(RawContacts._ID, contactId); 72 73 final ContentValues phone = new ContentValues(); 74 phone.put(Data._ID, phoneId); 75 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 76 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 77 phone.put(Phone.TYPE, Phone.TYPE_HOME); 78 79 final Entity before = new Entity(contact); 80 before.addSubValue(Data.CONTENT_URI, phone); 81 return before; 82 } 83 84 /** 85 * Test that {@link EntityDelta#mergeAfter(EntityDelta)} correctly passes 86 * any changes through the {@link Parcel} object. This enforces that 87 * {@link EntityDelta} should be identical when serialized against the same 88 * "before" {@link Entity}. 89 */ 90 public void testParcelChangesNone() { 91 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 92 final EntityDelta source = EntityDelta.fromBefore(before); 93 final EntityDelta dest = EntityDelta.fromBefore(before); 94 95 // Merge modified values and assert they match 96 final EntityDelta merged = EntityDelta.mergeAfter(dest, source); 97 assertEquals("Unexpected change when merging", source, merged); 98 } 99 100 public void testParcelChangesInsert() { 101 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 102 final EntityDelta source = EntityDelta.fromBefore(before); 103 final EntityDelta dest = EntityDelta.fromBefore(before); 104 105 // Add a new row and pass across parcel, should be same 106 final ContentValues phone = new ContentValues(); 107 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 108 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 109 phone.put(Phone.TYPE, Phone.TYPE_WORK); 110 source.addEntry(ValuesDelta.fromAfter(phone)); 111 112 // Merge modified values and assert they match 113 final EntityDelta merged = EntityDelta.mergeAfter(dest, source); 114 assertEquals("Unexpected change when merging", source, merged); 115 } 116 117 public void testParcelChangesUpdate() { 118 // Update existing row and pass across parcel, should be same 119 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 120 final EntityDelta source = EntityDelta.fromBefore(before); 121 final EntityDelta dest = EntityDelta.fromBefore(before); 122 123 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 124 child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 125 126 // Merge modified values and assert they match 127 final EntityDelta merged = EntityDelta.mergeAfter(dest, source); 128 assertEquals("Unexpected change when merging", source, merged); 129 } 130 131 public void testParcelChangesDelete() { 132 // Delete a row and pass across parcel, should be same 133 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 134 final EntityDelta source = EntityDelta.fromBefore(before); 135 final EntityDelta dest = EntityDelta.fromBefore(before); 136 137 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 138 child.markDeleted(); 139 140 // Merge modified values and assert they match 141 final EntityDelta merged = EntityDelta.mergeAfter(dest, source); 142 assertEquals("Unexpected change when merging", source, merged); 143 } 144 145 /** 146 * Test that {@link ValuesDelta#buildDiff(android.net.Uri)} is correctly 147 * built for insert, update, and delete cases. Note this only tests behavior 148 * for individual {@link Data} rows. 149 */ 150 public void testValuesDiffNone() { 151 final ContentValues before = new ContentValues(); 152 before.put(Data._ID, TEST_PHONE_ID); 153 before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 154 155 final ValuesDelta values = ValuesDelta.fromBefore(before); 156 157 // None action shouldn't produce a builder 158 final Builder builder = values.buildDiff(Data.CONTENT_URI); 159 assertNull("None action produced a builder", builder); 160 } 161 162 public void testValuesDiffInsert() { 163 final ContentValues after = new ContentValues(); 164 after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 165 166 final ValuesDelta values = ValuesDelta.fromAfter(after); 167 168 // Should produce an insert action 169 final Builder builder = values.buildDiff(Data.CONTENT_URI); 170 final int type = builder.build().getType(); 171 assertEquals("Didn't produce insert action", TYPE_INSERT, type); 172 } 173 174 public void testValuesDiffUpdate() { 175 final ContentValues before = new ContentValues(); 176 before.put(Data._ID, TEST_PHONE_ID); 177 before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 178 179 final ValuesDelta values = ValuesDelta.fromBefore(before); 180 values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 181 182 // Should produce an update action 183 final Builder builder = values.buildDiff(Data.CONTENT_URI); 184 final int type = builder.build().getType(); 185 assertEquals("Didn't produce update action", TYPE_UPDATE, type); 186 } 187 188 public void testValuesDiffDelete() { 189 final ContentValues before = new ContentValues(); 190 before.put(Data._ID, TEST_PHONE_ID); 191 before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 192 193 final ValuesDelta values = ValuesDelta.fromBefore(before); 194 values.markDeleted(); 195 196 // Should produce a delete action 197 final Builder builder = values.buildDiff(Data.CONTENT_URI); 198 final int type = builder.build().getType(); 199 assertEquals("Didn't produce delete action", TYPE_DELETE, type); 200 } 201 202 /** 203 * Test that {@link EntityDelta#buildDiff(ArrayList)} is correctly built for 204 * insert, update, and delete cases. This only tests a subset of possible 205 * {@link Data} row changes. 206 */ 207 public void testEntityDiffNone() { 208 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 209 final EntityDelta source = EntityDelta.fromBefore(before); 210 211 // Assert that writing unchanged produces few operations 212 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 213 source.buildDiff(diff); 214 215 assertTrue("Created changes when none needed", (diff.size() == 0)); 216 } 217 218 public void testEntityDiffNoneInsert() { 219 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 220 final EntityDelta source = EntityDelta.fromBefore(before); 221 222 // Insert a new phone number 223 final ContentValues phone = new ContentValues(); 224 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 225 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 226 phone.put(Phone.TYPE, Phone.TYPE_WORK); 227 source.addEntry(ValuesDelta.fromAfter(phone)); 228 229 // Assert two operations: insert Data row and enforce version 230 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 231 source.buildAssert(diff); 232 source.buildDiff(diff); 233 assertEquals("Unexpected operations", 4, diff.size()); 234 { 235 final ContentProviderOperation oper = diff.get(0); 236 assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); 237 } 238 { 239 final ContentProviderOperation oper = diff.get(1); 240 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 241 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 242 } 243 { 244 final ContentProviderOperation oper = diff.get(2); 245 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 246 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 247 } 248 { 249 final ContentProviderOperation oper = diff.get(3); 250 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 251 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 252 } 253 } 254 255 public void testEntityDiffUpdateInsert() { 256 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 257 final EntityDelta source = EntityDelta.fromBefore(before); 258 259 // Update parent contact values 260 source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED); 261 262 // Insert a new phone number 263 final ContentValues phone = new ContentValues(); 264 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 265 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 266 phone.put(Phone.TYPE, Phone.TYPE_WORK); 267 source.addEntry(ValuesDelta.fromAfter(phone)); 268 269 // Assert three operations: update Contact, insert Data row, enforce version 270 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 271 source.buildAssert(diff); 272 source.buildDiff(diff); 273 assertEquals("Unexpected operations", 5, diff.size()); 274 { 275 final ContentProviderOperation oper = diff.get(0); 276 assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); 277 } 278 { 279 final ContentProviderOperation oper = diff.get(1); 280 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 281 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 282 } 283 { 284 final ContentProviderOperation oper = diff.get(2); 285 assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); 286 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 287 } 288 { 289 final ContentProviderOperation oper = diff.get(3); 290 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 291 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 292 } 293 { 294 final ContentProviderOperation oper = diff.get(4); 295 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 296 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 297 } 298 } 299 300 public void testEntityDiffNoneUpdate() { 301 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 302 final EntityDelta source = EntityDelta.fromBefore(before); 303 304 // Update existing phone number 305 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 306 child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 307 308 // Assert that version is enforced 309 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 310 source.buildAssert(diff); 311 source.buildDiff(diff); 312 assertEquals("Unexpected operations", 4, diff.size()); 313 { 314 final ContentProviderOperation oper = diff.get(0); 315 assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); 316 } 317 { 318 final ContentProviderOperation oper = diff.get(1); 319 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 320 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 321 } 322 { 323 final ContentProviderOperation oper = diff.get(2); 324 assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); 325 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 326 } 327 { 328 final ContentProviderOperation oper = diff.get(3); 329 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 330 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 331 } 332 } 333 334 public void testEntityDiffDelete() { 335 final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID); 336 final EntityDelta source = EntityDelta.fromBefore(before); 337 338 // Delete entire entity 339 source.getValues().markDeleted(); 340 341 // Assert two operations: delete Contact and enforce version 342 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 343 source.buildAssert(diff); 344 source.buildDiff(diff); 345 assertEquals("Unexpected operations", 2, diff.size()); 346 { 347 final ContentProviderOperation oper = diff.get(0); 348 assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); 349 } 350 { 351 final ContentProviderOperation oper = diff.get(1); 352 assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); 353 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 354 } 355 } 356 357 public void testEntityDiffInsert() { 358 // Insert a RawContact 359 final ContentValues after = new ContentValues(); 360 after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); 361 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 362 363 final ValuesDelta values = ValuesDelta.fromAfter(after); 364 final EntityDelta source = new EntityDelta(values); 365 366 // Assert two operations: delete Contact and enforce version 367 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 368 source.buildAssert(diff); 369 source.buildDiff(diff); 370 assertEquals("Unexpected operations", 2, diff.size()); 371 { 372 final ContentProviderOperation oper = diff.get(0); 373 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 374 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 375 } 376 } 377 378 public void testEntityDiffInsertInsert() { 379 // Insert a RawContact 380 final ContentValues after = new ContentValues(); 381 after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); 382 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 383 384 final ValuesDelta values = ValuesDelta.fromAfter(after); 385 final EntityDelta source = new EntityDelta(values); 386 387 // Insert a new phone number 388 final ContentValues phone = new ContentValues(); 389 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 390 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 391 phone.put(Phone.TYPE, Phone.TYPE_WORK); 392 source.addEntry(ValuesDelta.fromAfter(phone)); 393 394 // Assert two operations: delete Contact and enforce version 395 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 396 source.buildAssert(diff); 397 source.buildDiff(diff); 398 assertEquals("Unexpected operations", 3, diff.size()); 399 { 400 final ContentProviderOperation oper = diff.get(0); 401 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 402 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 403 } 404 { 405 final ContentProviderOperation oper = diff.get(1); 406 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 407 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 408 409 } 410 } 411 } 412