Start of unit tests for ConversationCursor/Provider

* Includes modified TestProvider code originally intended for
  the Exchange application (as MockProvider)

Change-Id: I40126892c2177260a8eb1ace6e21e47a9ecf7665
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 5716697..05c1e69 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -36,6 +36,7 @@
 
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.UIProvider;
+import com.google.common.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -58,7 +59,8 @@
     // The cursor instantiator's activity
     private static Activity sActivity;
     // The cursor underlying the caching cursor
-    private static Cursor sUnderlyingCursor;
+    @VisibleForTesting
+    static Cursor sUnderlyingCursor;
     // The new cursor obtained via a requery
     private static Cursor sRequeryCursor;
     // A mapping from Uri to updated ContentValues
@@ -79,7 +81,8 @@
     // The listener registered for this cursor
     private static ConversationListener sListener;
     // The ConversationProvider instance
-    private static ConversationProvider sProvider;
+    @VisibleForTesting
+    static ConversationProvider sProvider;
     // Set when we're in the middle of a refresh of the underlying cursor
     private static boolean sRefreshInProgress = false;
     // Set when we've sent refreshReady() to listeners
@@ -756,14 +759,16 @@
             // Placeholder for now; there's no local insert
         }
 
-        private void deleteLocal(Uri uri) {
+        @VisibleForTesting
+        void deleteLocal(Uri uri) {
             Uri underlyingUri = uriFromCachingUri(uri);
             // Remember to decode the underlying Uri as it might be encoded (as w/ Gmail)
             String uriString =  Uri.decode(underlyingUri.toString());
             cacheValue(uriString, DELETED_COLUMN, true);
         }
 
-        private void updateLocal(Uri uri, ContentValues values) {
+        @VisibleForTesting
+        void updateLocal(Uri uri, ContentValues values) {
             Uri underlyingUri = uriFromCachingUri(uri);
             // Remember to decode the underlying Uri as it might be encoded (as w/ Gmail)
             String uriString =  Uri.decode(underlyingUri.toString());
diff --git a/tests/src/com/android/mail/browse/ConversationCursorTests.java b/tests/src/com/android/mail/browse/ConversationCursorTests.java
new file mode 100644
index 0000000..bb458aa
--- /dev/null
+++ b/tests/src/com/android/mail/browse/ConversationCursorTests.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.browse;
+
+import android.app.Activity;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.test.ProviderTestCase2;
+import android.test.mock.MockContentResolver;
+
+import com.android.mail.browse.ConversationCursor.ConversationProvider;
+
+import java.util.ArrayList;
+
+public class ConversationCursorTests extends ProviderTestCase2<TestProvider> {
+
+    public ConversationCursorTests(Class<TestProvider> providerClass, String providerAuthority) {
+        super(providerClass, providerAuthority);
+    }
+
+    Activity mActivity;
+    Context mMockContext;
+    MockContentResolver mMockResolver;
+
+    private static final String CONVO_TABLE = "convo";
+
+    public ConversationCursorTests() {
+        super(TestProvider.class, TestProvider.AUTHORITY);
+        mActivity = new Activity() {
+            public ContentResolver getContentResolver() {
+                return mMockResolver;
+            }
+        };
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mMockContext = getMockContext();
+        mMockResolver = (MockContentResolver)mMockContext.getContentResolver();
+        mMockResolver.addProvider(TestProvider.AUTHORITY, new TestProvider(mMockContext));
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private static final String SUBJECT_COLUMN = "subject";
+    private static final String FOLDER_COLUMN = "folder";
+    private static final String READ_COLUMN = "read";
+    private static final String STARRED_COLUMN = "starred";
+    private static final String URI_COLUMN = "uri";
+
+//    private static final int SUBJECT_INDEX = 0;
+//    private static final int FOLDER_INDEX = 1;
+//    private static final int READ_INDEX = 2;
+//    private static final int STARRED_INDEX = 3;
+    private static final int URI_INDEX = 4;
+
+    private static final String[] CONVO_PROJECTION = new String[] {
+        SUBJECT_COLUMN, FOLDER_COLUMN, READ_COLUMN, STARRED_COLUMN, URI_COLUMN};
+
+    private ContentValues makeConvo(String subject, String folder, int read, int starred) {
+        ContentValues cv = new ContentValues();
+        cv.put(SUBJECT_COLUMN, subject);
+        cv.put(FOLDER_COLUMN, folder);
+        cv.put(READ_COLUMN, read);
+        cv.put(STARRED_COLUMN, starred);
+        return cv;
+    }
+
+    private Uri setupConvoList() throws RemoteException, OperationApplicationException {
+        // The Uri is content://com.android.canhaz/pony
+        Uri uri = new Uri.Builder().scheme("content").authority(TestProvider.AUTHORITY)
+                .path(CONVO_TABLE).build();
+        // Our array of CPO's to be used with applyBatch
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        // Insert 9 convos
+        ContentValues values = makeConvo("Subj1", "Folder", 0, 0);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj2", "Folder", 0, 1);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj3", "Folder", 0, 0);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj4", "Folder", 0, 1);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj5", "Folder", 1, 0);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj6", "Folder", 1, 1);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj7", "Folder", 1, 0);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj8", "Folder", 1, 1);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        values = makeConvo("Subj9", "Folder", 1, 0);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
+        // Apply the batch with one insert operation
+        ContentProviderResult[] results = mMockResolver.applyBatch(TestProvider.AUTHORITY, ops);
+        ops.clear();
+        // Set up the uri column (basis of the cache)
+        for (ContentProviderResult result: results) {
+            ops.add(ContentProviderOperation
+                    .newUpdate(result.uri)
+                    .withValue(URI_COLUMN, result.uri.toString())
+                    .build());
+        }
+        mMockResolver.applyBatch(TestProvider.AUTHORITY, ops);
+        return uri;
+    }
+
+    public void testLocalDelete() throws RemoteException, OperationApplicationException {
+        Uri uri = setupConvoList();
+
+        // Now, get our CC
+        ConversationCursor cc =
+                ConversationCursor.create(mActivity, URI_COLUMN,
+                        uri, CONVO_PROJECTION, null, null, null);
+        ConversationProvider cp = ConversationCursor.sProvider;
+        Cursor uc = ConversationCursor.sUnderlyingCursor;
+        // First things first; cc & uc should both have 9 rows
+        assertEquals(9, cc.getCount());
+        assertEquals(9, uc.getCount());
+
+        // Get the uri's of our convos (the mock provider doesn't order rows)
+        String[] uris = new String[cc.getCount()];
+        int i = 0;
+        while (cc.moveToNext()) {
+            uris[i++] = cc.getString(URI_INDEX);
+        }
+
+        // Get a random uri (first will do)
+        String uriString = uris[0];
+        Uri ccUri = Uri.parse(uriString);
+        // It should have the authority of CP
+        assertEquals(ccUri.getAuthority(), ConversationProvider.AUTHORITY);
+
+        // Try deleting a row locally
+        cp.deleteLocal(Uri.parse(uris[4]));
+        assertEquals(8, cc.getCount());
+        assertEquals(9, uc.getCount());
+    }
+}
diff --git a/tests/src/com/android/mail/browse/TestProvider.java b/tests/src/com/android/mail/browse/TestProvider.java
new file mode 100644
index 0000000..42b2d66
--- /dev/null
+++ b/tests/src/com/android/mail/browse/TestProvider.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.browse;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * TestProvider is a ContentProvider that can be used to simulate the storage and retrieval of
+ * rows from any ContentProvider, even if that provider does not exist in the caller's package.
+ *
+ * It is specifically designed to enable testing of sync adapters that create
+ * ContentProviderOperations (CPOs) that are then executed using ContentResolver.applyBatch().
+ * Why is this useful? Because we can't instantiate CalendarProvider or ContactsProvider from our
+ * package, as required by MockContentResolver.addProvider()
+ *
+ * Usage:
+ *
+ * ContentResolver.applyBatch(MockProvider.AUTHORITY, batch) will cause the CPOs to be executed,
+ * returning an array of ContentProviderResult; in the case of inserts, the result will include
+ * a Uri that can be used via query(). Note that the CPOs can contain references to any authority.
+ *
+ * query() does not allow non-null selection, selectionArgs, or sortOrder arguments; the
+ * presence of these will result in an UnsupportedOperationException insert() acts as expected,
+ * returning a Uri that can be directly used in a query
+ *
+ * delete() and update() do not allow non-null selection or selectionArgs arguments; the presence
+ * of these will result in an UnsupportedOperationException
+ *
+ * NOTE: When using any operation other than applyBatch, the Uri to be used must be created with
+ * MockProvider.uri(yourUri). This guarantees that the operation is sent to MockProvider
+ *
+ * NOTE: MockProvider only simulates direct storage/retrieval of rows; it does not (and can not)
+ * simulate other actions (e.g. creation of ancillary data) that the actual provider might perform
+ *
+ * NOTE: See MockProviderTests for usage examples
+ **/
+public class TestProvider extends ContentProvider {
+    public static final String AUTHORITY = "com.android.mail.mock.provider";
+    /* package */static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    /* package */static final int TABLE = 100;
+    /* package */static final int RECORD = 101;
+
+    public static final String ID_COLUMN = "_id";
+
+    public TestProvider() {
+        super();
+    }
+
+    public TestProvider(Context context) {
+        this();
+        attachInfo(context, null);
+    }
+
+    // We'll store our values here
+    private HashMap<String, ContentValues> mMockStore = new HashMap<String, ContentValues>();
+    // And we'll generate new id's from here
+    long mMockId = 1;
+
+    /**
+     * Create a Uri for MockProvider from a given Uri
+     *
+     * @param uri the Uri from which the MockProvider Uri will be created
+     * @return a Uri that can be used with MockProvider
+     */
+    public static Uri uri(Uri uri) {
+        return new Uri.Builder().scheme("content").authority(AUTHORITY)
+                .path(uri.getPath().substring(1)).build();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (selection != null || selectionArgs != null) {
+            throw new UnsupportedOperationException();
+        }
+        String path = uri.getPath();
+        if (mMockStore.containsKey(path)) {
+            mMockStore.remove(path);
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        // Remove the leading slash
+        String table = uri.getPath().substring(1);
+        long id = mMockId++;
+        Uri newUri = new Uri.Builder().scheme("content").authority(AUTHORITY).path(table)
+                .appendPath(Long.toString(id)).build();
+        // Remember to store the _id
+        values.put(ID_COLUMN, id);
+        mMockStore.put(newUri.getPath(), values);
+        int match = sURIMatcher.match(uri);
+        if (match == UriMatcher.NO_MATCH) {
+            sURIMatcher.addURI(AUTHORITY, table, TABLE);
+            sURIMatcher.addURI(AUTHORITY, table + "/#", RECORD);
+        }
+        return newUri;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        if (selection != null || selectionArgs != null || sortOrder != null || projection == null) {
+            throw new UnsupportedOperationException();
+        }
+        final int match = sURIMatcher.match(uri(uri));
+        ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>();
+        switch (match) {
+            case TABLE:
+                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
+                String prefix = uri.getPath() + "/";
+                for (Entry<String, ContentValues> entry : entrySet) {
+                    if (entry.getKey().startsWith(prefix)) {
+                        valuesList.add(entry.getValue());
+                    }
+                }
+                break;
+            case RECORD:
+                ContentValues values = mMockStore.get(uri.getPath());
+                if (values != null) {
+                    valuesList.add(values);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        MatrixCursor cursor = new MatrixCursor(projection, 1);
+        for (ContentValues cv : valuesList) {
+            Object[] rowValues = new Object[projection.length];
+            int i = 0;
+            for (String column : projection) {
+                rowValues[i++] = cv.get(column);
+            }
+            cursor.addRow(rowValues);
+        }
+        return cursor;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs) {
+        if (selection != null || selectionArgs != null) {
+            throw new UnsupportedOperationException();
+        }
+        final int match = sURIMatcher.match(uri(uri));
+        ArrayList<ContentValues> updateValuesList = new ArrayList<ContentValues>();
+        String path = uri.getPath();
+        switch (match) {
+            case TABLE:
+                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
+                String prefix = path + "/";
+                for (Entry<String, ContentValues> entry : entrySet) {
+                    if (entry.getKey().startsWith(prefix)) {
+                        updateValuesList.add(entry.getValue());
+                    }
+                }
+                break;
+            case RECORD:
+                ContentValues cv = mMockStore.get(path);
+                if (cv != null) {
+                    updateValuesList.add(cv);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        Set<Entry<String, Object>> newValuesSet = newValues.valueSet();
+        for (Entry<String, Object> entry : newValuesSet) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            for (ContentValues targetValues : updateValuesList) {
+                if (value instanceof Integer) {
+                    targetValues.put(key, (Integer) value);
+                } else if (value instanceof Long) {
+                    targetValues.put(key, (Long) value);
+                } else if (value instanceof String) {
+                    targetValues.put(key, (String) value);
+                } else if (value instanceof Boolean) {
+                    targetValues.put(key, (Boolean) value);
+                } else {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+        for (ContentValues targetValues : updateValuesList) {
+            switch(match) {
+                case TABLE:
+                    mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues);
+                    break;
+                case RECORD:
+                    mMockStore.put(path, targetValues);
+                    break;
+            }
+        }
+        return updateValuesList.size();
+    }
+}
diff --git a/tests/src/com/android/mail/browse/TestProviderTests.java b/tests/src/com/android/mail/browse/TestProviderTests.java
new file mode 100644
index 0000000..8e3ff63
--- /dev/null
+++ b/tests/src/com/android/mail/browse/TestProviderTests.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.browse;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.test.ProviderTestCase2;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+
+@SmallTest
+public class TestProviderTests extends ProviderTestCase2<TestProvider> {
+    Context mMockContext;
+    MockContentResolver mMockResolver;
+
+    private static final String CANHAZ_AUTHORITY = "com.android.canhaz";
+    private static final String PONY_TABLE = "pony";
+    private static final String PONY_COLUMN_NAME = "name";
+    private static final String PONY_COLUMN_TYPE = "type";
+    private static final String PONY_COLUMN_LEGS= "legs";
+    private static final String PONY_COLUMN_CAN_RIDE = "canRide";
+    private static final String[] PONY_PROJECTION = {TestProvider.ID_COLUMN, PONY_COLUMN_NAME,
+        PONY_COLUMN_TYPE, PONY_COLUMN_LEGS, PONY_COLUMN_CAN_RIDE};
+    private static final int PONY_ID = 0;
+    private static final int PONY_NAME = 1;
+    private static final int PONY_TYPE = 2;
+    private static final int PONY_LEGS = 3;
+    private static final int PONY_CAN_RIDE = 4;
+
+    public TestProviderTests() {
+        super(TestProvider.class, TestProvider.AUTHORITY);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mMockContext = getMockContext();
+        mMockResolver = (MockContentResolver)mMockContext.getContentResolver();
+        mMockResolver.addProvider(CANHAZ_AUTHORITY, new TestProvider(mMockContext));
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private ContentValues ponyValues(String name, String type, int legs, boolean canRide) {
+        ContentValues cv = new ContentValues();
+        cv.put(PONY_COLUMN_NAME, name);
+        cv.put(PONY_COLUMN_TYPE, type);
+        cv.put(PONY_COLUMN_LEGS, legs);
+        cv.put(PONY_COLUMN_CAN_RIDE, canRide ? 1 : 0);
+        return cv;
+    }
+
+    private ContentProviderResult[] setupPonies() throws RemoteException,
+            OperationApplicationException {
+        // The Uri is content://com.android.canhaz/pony
+        Uri uri = new Uri.Builder().scheme("content").authority(CANHAZ_AUTHORITY)
+            .path(PONY_TABLE).build();
+        // Our array of CPO's to be used with applyBatch
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+
+        // Insert two ponies
+        ContentValues pony1 = ponyValues("Flicka", "wayward", 4, true);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(pony1).build());
+        ContentValues pony2 = ponyValues("Elise", "dastardly", 3, false);
+        ops.add(ContentProviderOperation.newInsert(uri).withValues(pony2).build());
+        // Apply the batch with one insert operation
+        return mMockResolver.applyBatch(TestProvider.AUTHORITY, ops);
+    }
+
+    private Uri getPonyUri() {
+        return new Uri.Builder().scheme("content").authority(CANHAZ_AUTHORITY)
+            .path(PONY_TABLE).build();
+    }
+
+    public void testInsertQueryandDelete() throws RemoteException, OperationApplicationException {
+        // The Uri is content://com.android.canhaz/pony
+        ContentProviderResult[] results = setupPonies();
+        Uri uri = getPonyUri();
+
+        // Check the results
+        assertNotNull(results);
+        assertEquals(2, results.length);
+        // Make sure that we've created matcher entries for pony and pony/#
+        assertEquals(TestProvider.TABLE, TestProvider.sURIMatcher.match(TestProvider.uri(uri)));
+        assertEquals(TestProvider.RECORD,
+                TestProvider.sURIMatcher.match(TestProvider.uri(results[0].uri)));
+        Cursor c = mMockResolver.query(TestProvider.uri(uri), PONY_PROJECTION, null, null, null);
+        assertNotNull(c);
+        assertEquals(2, c.getCount());
+        long eliseId = -1;
+        long flickaId = -1;
+        while (c.moveToNext()) {
+            String name = c.getString(PONY_NAME);
+            if ("Flicka".equals(name)) {
+                assertEquals("Flicka", c.getString(PONY_NAME));
+                assertEquals("wayward", c.getString(PONY_TYPE));
+                assertEquals(4, c.getInt(PONY_LEGS));
+                assertEquals(1, c.getInt(PONY_CAN_RIDE));
+                flickaId = c.getLong(PONY_ID);
+            } else if ("Elise".equals(name)) {
+                assertEquals("dastardly", c.getString(PONY_TYPE));
+                assertEquals(3, c.getInt(PONY_LEGS));
+                assertEquals(0, c.getInt(PONY_CAN_RIDE));
+                eliseId = c.getLong(PONY_ID);
+            } else {
+                fail("Wrong record: " + name);
+            }
+        }
+
+        // eliseId and flickaId should have been set
+        assertNotSame(-1, eliseId);
+        assertNotSame(-1, flickaId);
+        // Delete the elise record
+        assertEquals(1, mMockResolver.delete(ContentUris.withAppendedId(TestProvider.uri(uri),
+                eliseId), null, null));
+        c = mMockResolver.query(TestProvider.uri(uri), PONY_PROJECTION, null, null, null);
+        assertNotNull(c);
+        // There should be one left (Flicka)
+        assertEquals(1, c.getCount());
+        assertTrue(c.moveToNext());
+        assertEquals("Flicka", c.getString(PONY_NAME));
+    }
+
+    public void testUpdate() throws RemoteException, OperationApplicationException {
+        // The Uri is content://com.android.canhaz/pony
+        Uri uri = getPonyUri();
+        setupPonies();
+        Cursor c = mMockResolver.query(TestProvider.uri(uri), PONY_PROJECTION, null, null, null);
+        assertNotNull(c);
+        assertEquals(2, c.getCount());
+        // Give all the ponies 5 legs
+        ContentValues cv = new ContentValues();
+        cv.put(PONY_COLUMN_LEGS, 5);
+        assertEquals(2, mMockResolver.update(TestProvider.uri(uri), cv, null, null));
+        c = mMockResolver.query(TestProvider.uri(uri), PONY_PROJECTION, null, null, null);
+        assertNotNull(c);
+        // We should still have two records, and each should have 5 legs, but otherwise be the same
+        assertEquals(2, c.getCount());
+        long eliseId = -1;
+        long flickaId = -1;
+        while (c.moveToNext()) {
+            String name = c.getString(PONY_NAME);
+            if ("Flicka".equals(name)) {
+                assertEquals("Flicka", c.getString(PONY_NAME));
+                assertEquals("wayward", c.getString(PONY_TYPE));
+                assertEquals(5, c.getInt(PONY_LEGS));
+                assertEquals(1, c.getInt(PONY_CAN_RIDE));
+                flickaId = c.getLong(PONY_ID);
+            } else if ("Elise".equals(name)) {
+                assertEquals("dastardly", c.getString(PONY_TYPE));
+                assertEquals(5, c.getInt(PONY_LEGS));
+                assertEquals(0, c.getInt(PONY_CAN_RIDE));
+                eliseId = c.getLong(PONY_ID);
+            } else {
+                fail("Wrong record: " + name);
+            }
+        }
+        // eliseId and flickaId should have been set
+        assertNotSame(-1, eliseId);
+        assertNotSame(-1, flickaId);
+    }
+}