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 android.content;
18
19import android.content.ContentValues;
20import android.database.Cursor;
21import android.database.MatrixCursor;
22import android.net.Uri;
23import android.os.Parcel;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.text.TextUtils;
26import junit.framework.TestCase;
27
28import java.lang.reflect.Constructor;
29import java.lang.reflect.Field;
30import java.lang.reflect.InvocationTargetException;
31import java.util.HashMap;
32import java.util.Set;
33import java.util.Map;
34import java.util.Map.Entry;
35
36@SmallTest
37public class ContentProviderOperationTest extends TestCase {
38    private final static Uri sTestUri1 = Uri.parse("content://authority/blah");
39    private final static ContentValues sTestValues1;
40
41    private final static Class<ContentProviderOperation.Builder> CLASS_BUILDER =
42            ContentProviderOperation.Builder.class;
43    private final static Class<ContentProviderOperation> CLASS_OPERATION =
44            ContentProviderOperation.class;
45
46    static {
47        sTestValues1 = new ContentValues();
48        sTestValues1.put("a", 1);
49        sTestValues1.put("b", "two");
50    }
51
52    public void testInsert() throws OperationApplicationException {
53        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
54                .withValues(sTestValues1)
55                .build();
56        ContentProviderResult result = op1.apply(new TestContentProvider() {
57            public Uri insert(Uri uri, ContentValues values) {
58                assertEquals(sTestUri1.toString(), uri.toString());
59                assertEquals(sTestValues1.toString(), values.toString());
60                return uri.buildUpon().appendPath("19").build();
61            }
62        }, null, 0);
63        assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
64    }
65
66    public void testInsertNoValues() throws OperationApplicationException {
67        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
68                .build();
69        ContentProviderResult result = op1.apply(new TestContentProvider() {
70            public Uri insert(Uri uri, ContentValues values) {
71                assertEquals(sTestUri1.toString(), uri.toString());
72                assertNull(values);
73                return uri.buildUpon().appendPath("19").build();
74            }
75        }, null, 0);
76        assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
77    }
78
79    public void testInsertFailed() {
80        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
81                .withValues(sTestValues1)
82                .build();
83        try {
84            op1.apply(new TestContentProvider() {
85                public Uri insert(Uri uri, ContentValues values) {
86                    assertEquals(sTestUri1.toString(), uri.toString());
87                    assertEquals(sTestValues1.toString(), values.toString());
88                    return null;
89                }
90            }, null, 0);
91            fail("the apply should have thrown an OperationApplicationException");
92        } catch (OperationApplicationException e) {
93            // this is the expected case
94        }
95    }
96
97    public void testInsertWithBackRefs() throws OperationApplicationException {
98        ContentProviderResult[] previousResults = new ContentProviderResult[4];
99        previousResults[0] = new ContentProviderResult(100);
100        previousResults[1] = new ContentProviderResult(101);
101        previousResults[2] = new ContentProviderResult(102);
102        previousResults[3] = new ContentProviderResult(103);
103        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
104                .withValues(sTestValues1)
105                .withValueBackReference("a1", 3)
106                .withValueBackReference("a2", 1)
107                .build();
108        ContentProviderResult result = op1.apply(new TestContentProvider() {
109            public Uri insert(Uri uri, ContentValues values) {
110                assertEquals(sTestUri1.toString(), uri.toString());
111                ContentValues expected = new ContentValues(sTestValues1);
112                expected.put("a1", 103);
113                expected.put("a2", 101);
114                assertEquals(expected.toString(), values.toString());
115                return uri.buildUpon().appendPath("19").build();
116            }
117        }, previousResults, previousResults.length);
118        assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
119    }
120
121    public void testUpdate() throws OperationApplicationException {
122        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
123                .withValues(sTestValues1)
124                .build();
125        ContentProviderResult[] backRefs = new ContentProviderResult[2];
126        ContentProviderResult result = op1.apply(new TestContentProvider() {
127            public Uri insert(Uri uri, ContentValues values) {
128                assertEquals(sTestUri1.toString(), uri.toString());
129                assertEquals(sTestValues1.toString(), values.toString());
130                return uri.buildUpon().appendPath("19").build();
131            }
132        }, backRefs, 1);
133        assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
134    }
135
136    public void testAssert() {
137        // Build an operation to assert values match provider
138        ContentProviderOperation op1 = ContentProviderOperation.newAssertQuery(sTestUri1)
139                .withValues(sTestValues1).build();
140
141        try {
142            // Assert that values match from cursor
143            ContentProviderResult result = op1.apply(new TestContentProvider() {
144                public Cursor query(Uri uri, String[] projection, String selection,
145                        String[] selectionArgs, String sortOrder) {
146                    // Return cursor over specific set of values
147                    return getCursor(sTestValues1, 1);
148                }
149            }, null, 0);
150        } catch (OperationApplicationException e) {
151            fail("newAssert() failed");
152        }
153    }
154
155    public void testAssertNoValues() {
156        // Build an operation to assert values match provider
157        ContentProviderOperation op1 = ContentProviderOperation.newAssertQuery(sTestUri1)
158                .withExpectedCount(1).build();
159
160        try {
161            // Assert that values match from cursor
162            ContentProviderResult result = op1.apply(new TestContentProvider() {
163                public Cursor query(Uri uri, String[] projection, String selection,
164                        String[] selectionArgs, String sortOrder) {
165                    // Return cursor over specific set of values
166                    return getCursor(sTestValues1, 1);
167                }
168            }, null, 0);
169        } catch (OperationApplicationException e) {
170            fail("newAssert() failed");
171        }
172
173        ContentProviderOperation op2 = ContentProviderOperation.newAssertQuery(sTestUri1)
174                .withExpectedCount(0).build();
175
176        try {
177            // Assert that values match from cursor
178            ContentProviderResult result = op2.apply(new TestContentProvider() {
179                public Cursor query(Uri uri, String[] projection, String selection,
180                        String[] selectionArgs, String sortOrder) {
181                    // Return cursor over specific set of values
182                    return getCursor(sTestValues1, 0);
183                }
184            }, null, 0);
185        } catch (OperationApplicationException e) {
186            fail("newAssert() failed");
187        }
188
189        ContentProviderOperation op3 = ContentProviderOperation.newAssertQuery(sTestUri1)
190                .withExpectedCount(2).build();
191
192        try {
193            // Assert that values match from cursor
194            ContentProviderResult result = op3.apply(new TestContentProvider() {
195                public Cursor query(Uri uri, String[] projection, String selection,
196                        String[] selectionArgs, String sortOrder) {
197                    // Return cursor over specific set of values
198                    return getCursor(sTestValues1, 5);
199                }
200            }, null, 0);
201            fail("we expect the exception to be thrown");
202        } catch (OperationApplicationException e) {
203        }
204    }
205
206    /**
207     * Build a {@link Cursor} with a single row that contains all values
208     * provided through the given {@link ContentValues}.
209     */
210    private Cursor getCursor(ContentValues contentValues, int numRows) {
211        final Set<Entry<String, Object>> valueSet = contentValues.valueSet();
212        final String[] keys = new String[valueSet.size()];
213        final Object[] values = new Object[valueSet.size()];
214
215        int i = 0;
216        for (Entry<String, Object> entry : valueSet) {
217            keys[i] = entry.getKey();
218            values[i] = entry.getValue();
219            i++;
220        }
221
222        final MatrixCursor cursor = new MatrixCursor(keys);
223        for (i = 0; i < numRows; i++) {
224            cursor.addRow(values);
225        }
226        return cursor;
227    }
228
229    public void testValueBackRefs() {
230        ContentValues values = new ContentValues();
231        values.put("a", "in1");
232        values.put("a2", "in2");
233        values.put("b", "in3");
234        values.put("c", "in4");
235
236        ContentProviderResult[] previousResults = new ContentProviderResult[4];
237        previousResults[0] = new ContentProviderResult(100);
238        previousResults[1] = new ContentProviderResult(101);
239        previousResults[2] = new ContentProviderResult(102);
240        previousResults[3] = new ContentProviderResult(103);
241
242        ContentValues expectedValues = new ContentValues(values);
243        expectedValues.put("a1", (long) 103);
244        expectedValues.put("a2", (long) 101);
245        expectedValues.put("a3", (long) 102);
246
247        ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
248                .withValues(values)
249                .withValueBackReference("a1", 3)
250                .withValueBackReference("a2", 1)
251                .withValueBackReference("a3", 2)
252                .build();
253        ContentValues v2 = op1.resolveValueBackReferences(previousResults, previousResults.length);
254        assertEquals(expectedValues, v2);
255    }
256
257    public void testSelectionBackRefs() {
258        ContentProviderResult[] previousResults = new ContentProviderResult[4];
259        previousResults[0] = new ContentProviderResult(100);
260        previousResults[1] = new ContentProviderResult(101);
261        previousResults[2] = new ContentProviderResult(102);
262        previousResults[3] = new ContentProviderResult(103);
263
264        String[] selectionArgs = new String[]{"a", null, null, "b", null};
265
266        final ContentValues values = new ContentValues();
267        values.put("unused", "unused");
268
269        ContentProviderOperation op1 = ContentProviderOperation.newUpdate(sTestUri1)
270                .withSelectionBackReference(1, 3)
271                .withSelectionBackReference(2, 1)
272                .withSelectionBackReference(4, 2)
273                .withSelection("unused", selectionArgs)
274                .withValues(values)
275                .build();
276        String[] s2 = op1.resolveSelectionArgsBackReferences(
277                previousResults, previousResults.length);
278        assertEquals("a,103,101,b,102", TextUtils.join(",", s2));
279    }
280
281    public void testParcelingOperation() throws NoSuchFieldException, IllegalAccessException,
282            NoSuchMethodException, InvocationTargetException, InstantiationException {
283        Parcel parcel = Parcel.obtain();
284        ContentProviderOperation op1;
285        ContentProviderOperation op2;
286
287        HashMap<Integer, Integer> selArgsBackRef = new HashMap<Integer, Integer>();
288        selArgsBackRef.put(1, 2);
289        selArgsBackRef.put(3, 4);
290
291        ContentValues values = new ContentValues();
292        values.put("v1", "val1");
293        values.put("v2", "43");
294
295        ContentValues valuesBackRef = new ContentValues();
296        values.put("v3", "val3");
297        values.put("v4", "44");
298
299        try {
300            ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
301                    Uri.parse("content://goo/bar"));
302
303            builderSetExpectedCount(builder, 42);
304            builderSetSelection(builder, "selection");
305            builderSetSelectionArgs(builder, new String[]{"a", "b"});
306            builderSetSelectionArgsBackReferences(builder, selArgsBackRef);
307            builderSetValues(builder, values);
308            builderSetValuesBackReferences(builder, valuesBackRef);
309
310            op1 = newOperationFromBuilder(builder);
311            op1.writeToParcel(parcel, 0);
312            parcel.setDataPosition(0);
313            op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
314
315            assertEquals(ContentProviderOperation.TYPE_INSERT, operationGetType(op2));
316            assertEquals("content://goo/bar", operationGetUri(op2).toString());
317            assertEquals(Integer.valueOf(42), operationGetExpectedCount(op2));
318            assertEquals("selection", operationGetSelection(op2));
319            assertEquals(2, operationGetSelectionArgs(op2).length);
320            assertEquals("a", operationGetSelectionArgs(op2)[0]);
321            assertEquals("b", operationGetSelectionArgs(op2)[1]);
322            assertEquals(values, operationGetValues(op2));
323            assertEquals(valuesBackRef, operationGetValuesBackReferences(op2));
324            assertEquals(2, operationGetSelectionArgsBackReferences(op2).size());
325            assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1));
326            assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3));
327        } finally {
328            parcel.recycle();
329        }
330
331        try {
332            ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(
333                    Uri.parse("content://goo/bar"));
334
335            builderSetSelectionArgsBackReferences(builder, selArgsBackRef);
336
337            op1 = newOperationFromBuilder(builder);
338            op1.writeToParcel(parcel, 0);
339            parcel.setDataPosition(0);
340            op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
341            assertEquals(ContentProviderOperation.TYPE_UPDATE, operationGetType(op2));
342            assertEquals("content://goo/bar", operationGetUri(op2).toString());
343            assertNull(operationGetExpectedCount(op2));
344            assertNull(operationGetSelection(op2));
345            assertNull(operationGetSelectionArgs(op2));
346            assertNull(operationGetValues(op2));
347            assertNull(operationGetValuesBackReferences(op2));
348            assertEquals(2, operationGetSelectionArgsBackReferences(op2).size());
349            assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1));
350            assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3));
351        } finally {
352            parcel.recycle();
353        }
354
355        try {
356            ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
357                    Uri.parse("content://goo/bar"));
358
359            op1 = newOperationFromBuilder(builder);
360            op1.writeToParcel(parcel, 0);
361            parcel.setDataPosition(0);
362            op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
363            assertEquals(ContentProviderOperation.TYPE_DELETE, operationGetType(op2));
364            assertEquals("content://goo/bar", operationGetUri(op2).toString());
365            assertNull(operationGetExpectedCount(op2));
366            assertNull(operationGetSelection(op2));
367            assertNull(operationGetSelectionArgs(op2));
368            assertNull(operationGetValues(op2));
369            assertNull(operationGetValuesBackReferences(op2));
370            assertNull(operationGetSelectionArgsBackReferences(op2));
371        } finally {
372            parcel.recycle();
373        }
374    }
375
376    private static ContentProviderOperation newOperationFromBuilder(
377            ContentProviderOperation.Builder builder)
378            throws NoSuchMethodException, InstantiationException, IllegalAccessException,
379            InvocationTargetException {
380        final Constructor constructor = CLASS_OPERATION.getDeclaredConstructor(CLASS_BUILDER);
381        constructor.setAccessible(true);
382        return (ContentProviderOperation) constructor.newInstance(builder);
383    }
384
385    private void builderSetSelectionArgsBackReferences(
386            ContentProviderOperation.Builder builder, HashMap<Integer, Integer> selArgsBackRef)
387            throws NoSuchFieldException, IllegalAccessException {
388        Field field;
389        field = CLASS_BUILDER.getDeclaredField("mSelectionArgsBackReferences");
390        field.setAccessible(true);
391        field.set(builder, selArgsBackRef);
392    }
393
394    private void builderSetValuesBackReferences(
395            ContentProviderOperation.Builder builder, ContentValues valuesBackReferences)
396            throws NoSuchFieldException, IllegalAccessException {
397        Field field;
398        field = CLASS_BUILDER.getDeclaredField("mValuesBackReferences");
399        field.setAccessible(true);
400        field.set(builder, valuesBackReferences);
401    }
402
403    private void builderSetSelection(
404            ContentProviderOperation.Builder builder, String selection)
405            throws NoSuchFieldException, IllegalAccessException {
406        Field field;
407        field = CLASS_BUILDER.getDeclaredField("mSelection");
408        field.setAccessible(true);
409        field.set(builder, selection);
410    }
411
412    private void builderSetSelectionArgs(
413            ContentProviderOperation.Builder builder, String[] selArgs)
414            throws NoSuchFieldException, IllegalAccessException {
415        Field field;
416        field = CLASS_BUILDER.getDeclaredField("mSelectionArgs");
417        field.setAccessible(true);
418        field.set(builder, selArgs);
419    }
420
421    private void builderSetValues(
422            ContentProviderOperation.Builder builder, ContentValues values)
423            throws NoSuchFieldException, IllegalAccessException {
424        Field field;
425        field = CLASS_BUILDER.getDeclaredField("mValues");
426        field.setAccessible(true);
427        field.set(builder, values);
428    }
429
430    private void builderSetExpectedCount(
431            ContentProviderOperation.Builder builder, Integer expectedCount)
432            throws NoSuchFieldException, IllegalAccessException {
433        Field field;
434        field = CLASS_BUILDER.getDeclaredField("mExpectedCount");
435        field.setAccessible(true);
436        field.set(builder, expectedCount);
437    }
438
439    private int operationGetType(ContentProviderOperation operation)
440            throws NoSuchFieldException, IllegalAccessException {
441        final Field field = CLASS_OPERATION.getDeclaredField("mType");
442        field.setAccessible(true);
443        return field.getInt(operation);
444    }
445
446    private Uri operationGetUri(ContentProviderOperation operation)
447            throws NoSuchFieldException, IllegalAccessException {
448        final Field field = CLASS_OPERATION.getDeclaredField("mUri");
449        field.setAccessible(true);
450        return (Uri) field.get(operation);
451    }
452
453    private String operationGetSelection(ContentProviderOperation operation)
454            throws NoSuchFieldException, IllegalAccessException {
455        final Field field = CLASS_OPERATION.getDeclaredField("mSelection");
456        field.setAccessible(true);
457        return (String) field.get(operation);
458    }
459
460    private String[] operationGetSelectionArgs(ContentProviderOperation operation)
461            throws NoSuchFieldException, IllegalAccessException {
462        final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgs");
463        field.setAccessible(true);
464        return (String[]) field.get(operation);
465    }
466
467    private ContentValues operationGetValues(ContentProviderOperation operation)
468            throws NoSuchFieldException, IllegalAccessException {
469        final Field field = CLASS_OPERATION.getDeclaredField("mValues");
470        field.setAccessible(true);
471        return (ContentValues) field.get(operation);
472    }
473
474    private Integer operationGetExpectedCount(ContentProviderOperation operation)
475            throws NoSuchFieldException, IllegalAccessException {
476        final Field field = CLASS_OPERATION.getDeclaredField("mExpectedCount");
477        field.setAccessible(true);
478        return (Integer) field.get(operation);
479    }
480
481    private ContentValues operationGetValuesBackReferences(ContentProviderOperation operation)
482            throws NoSuchFieldException, IllegalAccessException {
483        final Field field = CLASS_OPERATION.getDeclaredField("mValuesBackReferences");
484        field.setAccessible(true);
485        return (ContentValues) field.get(operation);
486    }
487
488    private Map<Integer, Integer> operationGetSelectionArgsBackReferences(
489            ContentProviderOperation operation)
490            throws NoSuchFieldException, IllegalAccessException {
491        final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgsBackReferences");
492        field.setAccessible(true);
493        return (Map<Integer, Integer>) field.get(operation);
494    }
495
496    public void testParcelingResult() {
497        Parcel parcel = Parcel.obtain();
498        ContentProviderResult result1;
499        ContentProviderResult result2;
500        try {
501            result1 = new ContentProviderResult(Uri.parse("content://goo/bar"));
502            result1.writeToParcel(parcel, 0);
503            parcel.setDataPosition(0);
504            result2 = ContentProviderResult.CREATOR.createFromParcel(parcel);
505            assertEquals("content://goo/bar", result2.uri.toString());
506            assertNull(result2.count);
507        } finally {
508            parcel.recycle();
509        }
510
511        parcel = Parcel.obtain();
512        try {
513            result1 = new ContentProviderResult(42);
514            result1.writeToParcel(parcel, 0);
515            parcel.setDataPosition(0);
516            result2 = ContentProviderResult.CREATOR.createFromParcel(parcel);
517            assertEquals(Integer.valueOf(42), result2.count);
518            assertNull(result2.uri);
519        } finally {
520            parcel.recycle();
521        }
522    }
523
524    static class TestContentProvider extends ContentProvider {
525        public boolean onCreate() {
526            throw new UnsupportedOperationException();
527        }
528
529        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
530                String sortOrder) {
531            throw new UnsupportedOperationException();
532        }
533
534        public String getType(Uri uri) {
535            throw new UnsupportedOperationException();
536        }
537
538        public Uri insert(Uri uri, ContentValues values) {
539            throw new UnsupportedOperationException();
540        }
541
542        public int delete(Uri uri, String selection, String[] selectionArgs) {
543            throw new UnsupportedOperationException();
544        }
545
546        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
547            throw new UnsupportedOperationException();
548        }
549    }
550}