1 /*
2  * Copyright (C) 2013 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 android.provider.cts.contacts;
18 
19 import android.accounts.Account;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.database.Cursor;
23 import android.net.Uri;
24 import android.provider.BaseColumns;
25 import android.provider.ContactsContract.Contacts;
26 import android.provider.cts.contacts.account.StaticAccountAuthenticator;
27 import android.test.MoreAsserts;
28 
29 import junit.framework.Assert;
30 
31 import java.util.BitSet;
32 
33 /**
34  * Common methods for asserting database related operations.
35  */
36 public class DatabaseAsserts {
37 
assertDeleteIsUnsupported(ContentResolver resolver, Uri uri)38     public static void assertDeleteIsUnsupported(ContentResolver resolver, Uri uri) {
39         try {
40             resolver.delete(uri, null, null);
41             Assert.fail("delete operation should have failed with UnsupportedOperationException on"
42                     + uri);
43         } catch (UnsupportedOperationException e) {
44             // pass
45         }
46     }
47 
assertInsertIsUnsupported(ContentResolver resolver, Uri uri)48     public static void assertInsertIsUnsupported(ContentResolver resolver, Uri  uri) {
49         try {
50             ContentValues values = new ContentValues();
51             resolver.insert(uri, values);
52             Assert.fail("insert operation should have failed with UnsupportedOperationException on"
53                     + uri);
54         } catch (UnsupportedOperationException e) {
55             // pass
56         }
57     }
58 
59     /**
60      * Create a contact in account 1 and assert that the record exists.
61      *
62      * @return The created contact id pair.
63      */
assertAndCreateContact(ContentResolver resolver)64     public static ContactIdPair assertAndCreateContact(ContentResolver resolver) {
65         return assertAndCreateContact(resolver, StaticAccountAuthenticator.ACCOUNT_1);
66     }
67 
68     /**
69      * Create a contact in a specified account and assert that the record exists.
70      *
71      * @return The created contact id pair.
72      */
assertAndCreateContactWithName(ContentResolver resolver, Account account, String name)73     public static ContactIdPair assertAndCreateContactWithName(ContentResolver resolver,
74             Account account, String name) {
75         long rawContactId = RawContactUtil.createRawContactWithName(resolver, account, name);
76 
77         long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId);
78         MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
79 
80         return new ContactIdPair(contactId, rawContactId);
81     }
82 
83     /**
84      * Create a contact in a specified account and assert that the record exists.
85      *
86      * @return The created contact id pair.
87      */
assertAndCreateContact(ContentResolver resolver, Account account)88     public static ContactIdPair assertAndCreateContact(ContentResolver resolver, Account account) {
89         long rawContactId = RawContactUtil.createRawContactWithAutoGeneratedName(resolver, account);
90 
91         long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId);
92         MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
93 
94         return new ContactIdPair(contactId, rawContactId);
95     }
96 
97     /**
98      * Asserts that a contact id was deleted, has a delete log, and that log has a timestamp greater
99      * than the given timestamp.
100      *
101      * @param contactId The contact id to check.
102      * @param start The timestamp that the delete log should be greater than.
103      */
assertHasDeleteLogGreaterThan(ContentResolver resolver, long contactId, long start)104     public static void assertHasDeleteLogGreaterThan(ContentResolver resolver, long contactId,
105             long start) {
106         Assert.assertFalse(ContactUtil.recordExistsForContactId(resolver, contactId));
107 
108         long deletedTimestamp = DeletedContactUtil.queryDeletedTimestampForContactId(resolver,
109                 contactId);
110         MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, deletedTimestamp);
111         Assert.assertTrue(deletedTimestamp > start);
112     }
113 
114     /**
115      * Holds a single contact id and raw contact id relationship.
116      */
117     public static class ContactIdPair {
118         public long mContactId;
119         public long mRawContactId;
120 
ContactIdPair(long contactId, long rawContactId)121         public ContactIdPair(long contactId, long rawContactId) {
122             this.mContactId = contactId;
123             this.mRawContactId = rawContactId;
124         }
125 
126         @Override
toString()127         public String toString() {
128             return "ContactIdPair{" +
129                     "mContactId=" + mContactId +
130                     ", mRawContactId=" + mRawContactId +
131                     '}';
132         }
133     }
134 
135     /**
136      * Queries for a given {@link Uri} against a provided {@link ContentResolver}, and
137      * ensures that the returned cursor contains exactly the expected values.
138      *
139      * @param resolver - ContentResolver to query against
140      * @param uri - {@link Uri} to perform the query for
141      * contained in <code>expectedValues</code> in order for the assert to pass.
142      * @param expectedValues - Array of {@link ContentValues} which the cursor returned from the
143      * query should contain.
144      */
assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, ContentValues... expectedValues)145     public static void assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri,
146             ContentValues... expectedValues) {
147         assertStoredValuesInUriMatchExactly(resolver, uri, null, null, null, null, false, expectedValues);
148     }
149 
150     /**
151      * Queries for a given {@link Uri} against a provided {@link ContentResolver}, and
152      * ensures that the returned cursor contains exactly the expected values.
153      *
154      * @param resolver - ContentResolver to query against
155      * @param uri - {@link Uri} to perform the query for
156      * @param projection - Projection to use for the query. Must contain at least the columns
157      * contained in <code>expectedValues</code> in order for the assert to pass.
158      * @param selection - Selection string to use for the query.
159      * @param selectionArgs - Selection arguments to use for the query.
160      * @param sortOrder - Sort order to use for the query.
161      * @param inOrder Whether or not the returned rows in the cursor should correspond to the
162      * order of the provided ContentValues
163      * @param expectedValues - Array of {@link ContentValues} which the cursor returned from the
164      * query should contain.
165      */
assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, boolean inOrder, ContentValues... expectedValues)166     public static void assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri,
167             String[] projection, String selection, String[] selectionArgs, String sortOrder,
168             boolean inOrder, ContentValues... expectedValues) {
169         final Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, sortOrder);
170         try {
171             if (inOrder) {
172                 assertCursorValuesMatchExactlyInOrder(cursor, expectedValues);
173             } else {
174                 assertCursorValuesMatchExactly(cursor, expectedValues);
175             }
176         } finally {
177             cursor.close();
178         }
179     }
180 
181     /**
182      * Ensures that the rows in the cursor match the rows in the expected values exactly. However,
183      * does not require that the rows in the cursor are ordered the same way as those in the
184      * expected values.
185      *
186      * @param cursor - Cursor containing the values to check for
187      * @param expectedValues - Array of ContentValues that the cursor should be expected to
188      * contain.
189      */
assertCursorValuesMatchExactly(Cursor cursor, ContentValues... expectedValues)190     public static void assertCursorValuesMatchExactly(Cursor cursor,
191             ContentValues... expectedValues) {
192         Assert.assertEquals("Cursor does not contain the number of expected rows",
193                 expectedValues.length, cursor.getCount());
194         StringBuilder message = new StringBuilder();
195         // In case if expectedValues contains multiple identical values, remember which cursor
196         // rows are "consumed" to prevent multiple ContentValues from hitting the same row.
197         final BitSet used = new BitSet(cursor.getCount());
198 
199         for (ContentValues v : expectedValues) {
200             boolean found = false;
201             cursor.moveToPosition(-1);
202             while (cursor.moveToNext()) {
203                 final int pos = cursor.getPosition();
204                 if (used.get(pos)) continue;
205                 found = equalsWithExpectedValues(cursor, v, message);
206                 if (found) {
207                     used.set(pos);
208                     break;
209                 }
210             }
211             Assert.assertTrue("Expected values can not be found " + v + "," + message.toString(),
212                     found);
213         }
214     }
215 
216     /**
217      * Ensures that the rows in the cursor match the rows in the expected values exactly. Requires
218      * that the rows in the cursor are ordered the same way as those in the expected values.
219      *
220      * @param cursor - Cursor containing the values to check for
221      * @param expectedValues - Array of ContentValues that the cursor should be expected to
222      * contain.
223      */
assertCursorValuesMatchExactlyInOrder(Cursor cursor, ContentValues... expectedValues)224     public static void assertCursorValuesMatchExactlyInOrder(Cursor cursor,
225             ContentValues... expectedValues) {
226         Assert.assertEquals("Cursor does not contain the number of expected rows",
227                 expectedValues.length, cursor.getCount());
228         StringBuilder message = new StringBuilder();
229 
230         cursor.moveToPosition(-1);
231         for (ContentValues v : expectedValues) {
232             cursor.moveToNext();
233             Assert.assertTrue("Expected values can not be found " + v + "," + message.toString(),
234                     equalsWithExpectedValues(cursor, v, message));
235         }
236     }
237 
238 
equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues, StringBuilder msgBuffer)239     private static boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
240             StringBuilder msgBuffer) {
241         for (String column : expectedValues.keySet()) {
242             int index = cursor.getColumnIndex(column);
243             if (index == -1) {
244                 msgBuffer.append(" No such column: ").append(column);
245                 return false;
246             }
247             Object expectedValue = expectedValues.get(column);
248             String value;
249             expectedValue = expectedValues.getAsString(column);
250             value = cursor.getString(cursor.getColumnIndex(column));
251             if (expectedValue != null && !expectedValue.equals(value) || value != null
252                     && !value.equals(expectedValue)) {
253                 msgBuffer
254                         .append(" Column value ")
255                         .append(column)
256                         .append(" expected <")
257                         .append(expectedValue)
258                         .append(">, but was <")
259                         .append(value)
260                         .append('>');
261                 return false;
262             }
263         }
264         return true;
265     }
266 
buildIdSelection(long[] ids)267     public static String buildIdSelection(long[] ids) {
268         StringBuilder selection = new StringBuilder();
269         selection.append(BaseColumns._ID + " in ");
270         selection.append("(");
271         for (int i = 0; i < ids.length; i++) {
272             if (i != 0) selection.append(",");
273             selection.append(ids[i]);
274         }
275         selection.append(")");
276         return selection.toString();
277     }
278 
279     /**
280      * Check if a query accepts all columns in the projection, and a resulting cursor contains
281      * all expected columns.  This also checks the number of resulting rows, but don't check
282      * actual content in a returned cursor.
283      */
checkProjection(ContentResolver resolver, Uri uri, String[] allColumns, long[] ids)284     public static void checkProjection(ContentResolver resolver,
285             Uri uri, String[] allColumns, long[] ids) {
286         final String selection = buildIdSelection(ids);
287 
288         // First, check the null projection (i.e. all columns).
289         checkProjectionInner(resolver, uri, null, allColumns, selection, ids.length,
290                 /* keepPosition =*/ false);
291 
292         // All columns.
293         checkProjectionInner(resolver, uri, allColumns, allColumns, selection, ids.length,
294                 /* keepPosition =*/ true);
295 
296         // Select each column one by one.
297         for (int i = 0; i < allColumns.length; i++) {
298             final String[] columns = new String[] {allColumns[i]};
299             checkProjectionInner(resolver, uri, columns, columns, selection, ids.length,
300                         /* keepPosition =*/ true);
301         }
302 
303         // Select two columns.
304         for (int i = 0; i < allColumns.length; i++) {
305             for (int j = 0; j < allColumns.length; j++) {
306                 // Requesting the same column multiple times is okay, but it'd make the column
307                 // order check harder.
308                 if (i == j) continue;
309                 final String[] columns = new String[] {allColumns[i], allColumns[j]};
310                 checkProjectionInner(resolver, uri, columns, columns, selection, ids.length,
311                         /* keepPosition =*/ true);
312             }
313         }
314     }
315 
checkProjectionInner(ContentResolver resolver, Uri uri, String[] projection, String[] expectedColumns, String selection, int expectedRowCount, boolean keepPosition)316     private static void checkProjectionInner(ContentResolver resolver,
317             Uri uri, String[] projection, String[] expectedColumns,
318             String selection, int expectedRowCount, boolean keepPosition) {
319         final Cursor c = resolver.query(uri, projection, selection,
320                 /* args =*/ null, /* sort =*/ null);
321         Assert.assertNotNull(c);
322         try {
323             Assert.assertEquals("# of rows", expectedRowCount, c.getCount());
324 
325             // Make sure expected columns exist.
326             for (int i = 0; i < expectedColumns.length; i++) {
327                 final String column = expectedColumns[i];
328                 if (keepPosition) {
329                     Assert.assertEquals(column, c.getColumnName(i));
330                 } else {
331                     Assert.assertTrue(column, c.getColumnIndex(column) >= 0);
332                 }
333             }
334         } finally {
335             c.close();
336         }
337     }
338 }
339