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