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