mobile/android/base/tests/testReadingListProvider.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.tests;
     7 import java.util.HashSet;
     8 import java.util.Random;
     9 import java.util.concurrent.Callable;
    11 import org.mozilla.gecko.db.BrowserContract;
    12 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
    13 import org.mozilla.gecko.db.ReadingListProvider;
    15 import android.content.ContentProvider;
    16 import android.content.ContentUris;
    17 import android.content.ContentValues;
    18 import android.database.Cursor;
    19 import android.database.sqlite.SQLiteDatabase;
    20 import android.net.Uri;
    22 public class testReadingListProvider extends ContentProviderTest {
    24     private static final String DB_NAME = "browser.db";
    26     // List of tests to be run sorted by dependency.
    27     private final TestCase[] TESTS_TO_RUN = { new TestInsertItems(),
    28                                               new TestDeleteItems(),
    29                                               new TestUpdateItems(),
    30                                               new TestBatchOperations(),
    31                                               new TestBrowserProviderNotifications() };
    33     // Columns used to test for item equivalence.
    34     final String[] TEST_COLUMNS = { ReadingListItems.TITLE,
    35                                     ReadingListItems.URL,
    36                                     ReadingListItems.EXCERPT,
    37                                     ReadingListItems.LENGTH,
    38                                     ReadingListItems.DATE_CREATED };
    40     // Indicates that insertions have been tested. ContentProvider.insert
    41     // has been proven to work.
    42     private boolean mContentProviderInsertTested = false;
    44     // Indicates that updates have been tested. ContentProvider.update
    45     // has been proven to work.
    46     private boolean mContentProviderUpdateTested = false;
    48     /**
    49      * Factory function that makes new ReadingListProvider instances.
    50      * <p>
    51      * We want a fresh provider each test, so this should be invoked in
    52      * <code>setUp</code> before each individual test.
    53      */
    54     private static Callable<ContentProvider> sProviderFactory = new Callable<ContentProvider>() {
    55         @Override
    56         public ContentProvider call() {
    57             return new ReadingListProvider();
    58         }
    59     };
    61     @Override
    62     public void setUp() throws Exception {
    63         super.setUp(sProviderFactory, BrowserContract.READING_LIST_AUTHORITY, DB_NAME);
    64         for (TestCase test: TESTS_TO_RUN) {
    65             mTests.add(test);
    66         }
    67     }
    69     public void testReadingListProviderTests() throws Exception {
    70         for (Runnable test : mTests) {
    71             setTestName(test.getClass().getSimpleName());
    72             ensureEmptyDatabase();
    73             test.run();
    74         }
    76         // Ensure browser initialization is complete before completing test,
    77         // so that the minidumps directory is consistently created.
    78         blockForGeckoReady();
    79     }
    81     /**
    82      * Verify that we can insert a reading list item into the DB.
    83      */
    84     private class TestInsertItems extends TestCase {
    85         @Override
    86         public void test() throws Exception {
    87             ContentValues b = createFillerReadingListItem();
    88             long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
    89             Cursor c = getItemById(id);
    91             try {
    92                 mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
    93                 assertRowEqualsContentValues(c, b);
    94             } finally {
    95                 c.close();
    96             }
    98             testInsertWithNullCol(ReadingListItems.GUID);
    99             mContentProviderInsertTested = true;
   100         }
   102         /**
   103          * Test that insertion fails when a required column
   104          * is null.
   105          */
   106         private void testInsertWithNullCol(String colName) {
   107             ContentValues b = createFillerReadingListItem();
   108             b.putNull(colName);
   110             try {
   111                 ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
   112                 // If we get to here, the flawed insertion succeeded. Fail the test.
   113                 mAsserter.ok(false, "Insertion did not succeed with " + colName + " == null", "");
   114             } catch (NullPointerException e) {
   115                 // Indicates test was successful.
   116             }
   117         }
   118     }
   120     /**
   121      * Verify that we can remove a reading list item from the DB.
   122      */
   123     private class TestDeleteItems extends TestCase {
   125         @Override
   126         public void test() throws Exception {
   127             long id = insertAnItemWithAssertion();
   128             // Test that the item is only marked as deleted and
   129             // not removed from the database.
   130             testNonFirefoxSyncDelete(id);
   132             // Test that the item is removed from the database.
   133             testFirefoxSyncDelete(id);
   135             id = insertAnItemWithAssertion();
   136             // Test that deleting works with only a URI.
   137             testDeleteWithItemURI(id);
   138         }
   140         /**
   141          * Delete an item with PARAM_IS_SYNC unset and verify that item was only marked
   142          * as deleted and not actually removed from the database. Also verify that the item
   143          * marked as deleted doesn't show up in a query.
   144          *
   145          * @param id of the item to be deleted
   146          */
   147         private void testNonFirefoxSyncDelete(long id) {
   148             final int deleted = mProvider.delete(ReadingListItems.CONTENT_URI,
   149                     ReadingListItems._ID + " = ?",
   150                     new String[] { String.valueOf(id) });
   152             mAsserter.is(deleted, 1, "Inserted item was deleted");
   154             // PARAM_SHOW_DELETED in the URI allows items marked as deleted to be
   155             // included in the query.
   156             Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
   157             assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
   159             // Test that the 'deleted' item does not show up in a query when PARAM_SHOW_DELETED
   160             // is not specified in the URI.
   161             assertItemDoesNotExistByID(id, "Inserted item can't be found after deletion");
   162         }
   164         /**
   165          * Delete an item with PARAM_IS_SYNC=1 and verify that item
   166          * was actually removed from the database.
   167          *
   168          * @param id of the item to be deleted
   169          */
   170         private void testFirefoxSyncDelete(long id) {
   171             final int deleted = mProvider.delete(appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
   172                     ReadingListItems._ID + " = ?",
   173                     new String[] { String.valueOf(id) });
   175             mAsserter.is(deleted, 1, "Inserted item was deleted");
   177             Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
   178             assertItemDoesNotExistByID(uri, id, "Inserted item is now actually deleted");
   179         }
   181         /**
   182          * Delete an item with its URI and verify that the item
   183          * was actually removed from the database.
   184          *
   185          * @param id of the item to be deleted
   186          */
   187         private void testDeleteWithItemURI(long id) {
   188             final int deleted = mProvider.delete(ContentUris.withAppendedId(ReadingListItems.CONTENT_URI, id), null, null);
   189             mAsserter.is(deleted, 1, "Inserted item was deleted using URI with id");
   190         }
   191     }
   193     /**
   194      * Verify that we can update reading list items.
   195      */
   196     private class TestUpdateItems extends TestCase {
   198         @Override
   199         public void test() throws Exception {
   200             // We should be able to insert into the DB.
   201             ensureCanInsert();
   203             ContentValues original = createFillerReadingListItem();
   204             long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, original));
   205             int updated = 0;
   206             Long originalDateCreated = null;
   207             Long originalDateModified = null;
   208             ContentValues updates = new ContentValues();
   209             Cursor c = getItemById(id);
   210             try {
   211                 mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
   213                 originalDateCreated = c.getLong(c.getColumnIndex(ReadingListItems.DATE_CREATED));
   214                 originalDateModified = c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED));
   216                 updates.put(ReadingListItems.TITLE, original.getAsString(ReadingListItems.TITLE) + "CHANGED");
   217                 updates.put(ReadingListItems.URL, original.getAsString(ReadingListItems.URL) + "/more/stuff");
   218                 updates.put(ReadingListItems.EXCERPT, original.getAsString(ReadingListItems.EXCERPT) + "CHANGED");
   220                 updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
   221                                                ReadingListItems._ID + " = ?",
   222                                                new String[] { String.valueOf(id) });
   224                 mAsserter.is(updated, 1, "Inserted item was updated");
   225             } finally {
   226                 c.close();
   227             }
   229             // Name change for clarity. These values will be compared with the
   230             // current cursor row.
   231             ContentValues expectedValues = updates;
   232             c = getItemById(id);
   233             try {
   234                 mAsserter.ok(c.moveToFirst(), "Updated item found", "");
   235                 mAsserter.isnot(c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED)),
   236                 originalDateModified,
   237                 "Date modified should have changed");
   239                 // DATE_CREATED and LENGTH should equal old values since they weren't updated.
   240                 expectedValues.put(ReadingListItems.DATE_CREATED, originalDateCreated);
   241                 expectedValues.put(ReadingListItems.LENGTH, original.getAsString(ReadingListItems.LENGTH));
   242                 assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false);
   243             } finally {
   244                 c.close();
   245             }
   247             // Test that updates on an item that doesn't exist does not modify any rows.
   248             testUpdateWithInvalidID();
   250             // Test that update fails when a GUID is null.
   251             testUpdateWithNullCol(id, ReadingListItems.GUID);
   253             mContentProviderUpdateTested = true;
   254         }
   256         /**
   257          * Test that updates on an item that doesn't exist does
   258          * not modify any rows.
   259          *
   260          * @param id of the item to be deleted
   261          */
   262         private void testUpdateWithInvalidID() {
   263             ensureEmptyDatabase();
   264             final ContentValues b = createFillerReadingListItem();
   265             final long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
   266             final long INVALID_ID = id + 1;
   267             final ContentValues updates = new ContentValues();
   268             updates.put(ReadingListItems.TITLE, b.getAsString(ReadingListItems.TITLE) + "CHANGED");
   269             final int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
   270                                                ReadingListItems._ID + " = ?",
   271                                                new String[] { String.valueOf(INVALID_ID) });
   272             mAsserter.is(updated, 0, "Should not be able to update item with an invalid GUID");
   273         }
   275         /**
   276          * Test that update fails when a required column is null.
   277          */
   278         private int testUpdateWithNullCol(long id, String colName) {
   279             ContentValues updates = new ContentValues();
   280             updates.putNull(colName);
   282             int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
   283                                            ReadingListItems._ID + " = ?",
   284                                            new String[] { String.valueOf(id) });
   286             mAsserter.is(updated, 0, "Should not be able to update item with " + colName + " == null ");
   287             return updated;
   288         }
   289     }
   291     private class TestBatchOperations extends TestCase {
   292         private static final int ITEM_COUNT = 10;
   294         /**
   295          * Insert a bunch of items into the DB with the bulkInsert
   296          * method and verify that they are there.
   297          */
   298         private void testBulkInsert() {
   299             ensureEmptyDatabase();
   300             final ContentValues allVals[] = new ContentValues[ITEM_COUNT];
   301             final HashSet<String> urls = new HashSet<String>();
   302             for (int i = 0; i < ITEM_COUNT; i++) {
   303                 final String url =  "http://www.test.org/" + i;
   304                 allVals[i] = new ContentValues();
   305                 allVals[i].put(ReadingListItems.TITLE, "Test" + i);
   306                 allVals[i].put(ReadingListItems.URL, url);
   307                 allVals[i].put(ReadingListItems.EXCERPT, "EXCERPT" + i);
   308                 allVals[i].put(ReadingListItems.LENGTH, i);
   309                 urls.add(url);
   310             }
   312             int inserts = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, allVals);
   313             mAsserter.is(inserts, ITEM_COUNT, "Excepted number of inserts matches");
   315             Cursor c = mProvider.query(ReadingListItems.CONTENT_URI, null,
   316                                null,
   317                                null,
   318                                null);
   319             try {
   320                 while (c.moveToNext()) {
   321                     final String url = c.getString(c.getColumnIndex(ReadingListItems.URL));
   322                     mAsserter.ok(urls.contains(url), "Bulk inserted item with url == " + url + " was found in the DB", "");
   323                     // We should only be seeing each item once. Remove from set to prevent dups.
   324                     urls.remove(url);
   325                 }
   326             } finally {
   327                 c.close();
   328             }
   329         }
   331         @Override
   332         public void test() {
   333             testBulkInsert();
   334         }
   335     }
   337     /*
   338      * Verify that insert, update, delete, and bulkInsert operations
   339      * notify the ambient content resolver. Each operation calls the
   340      * content resolver notifyChange method synchronously, so it is
   341      * okay to test sequentially.
   342      */
   343     private class TestBrowserProviderNotifications extends TestCase {
   345         @Override
   346         public void test() {
   347             // We should be able to insert into the DB.
   348             ensureCanInsert();
   349             // We should be able to update the DB.
   350             ensureCanUpdate();
   352             final String CONTENT_URI = ReadingListItems.CONTENT_URI.toString();
   354             mResolver.notifyChangeList.clear();
   356             // Insert
   357             final ContentValues h = createFillerReadingListItem();
   358             long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, h));
   360             mAsserter.isnot(id,
   361                             -1L,
   362                             "Inserted item has valid id");
   364             ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "insert");
   366             // Update
   367             mResolver.notifyChangeList.clear();
   368             h.put(ReadingListItems.TITLE, "http://newexample.com");
   370             long numUpdated = mProvider.update(ReadingListItems.CONTENT_URI, h,
   371                                                ReadingListItems._ID + " = ?",
   372                                                new String[] { String.valueOf(id) });
   374             mAsserter.is(numUpdated,
   375                          1L,
   376                          "Correct number of items are updated");
   378             ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "update");
   380             // Delete
   381             mResolver.notifyChangeList.clear();
   382             long numDeleted = mProvider.delete(ReadingListItems.CONTENT_URI, null, null);
   384             mAsserter.is(numDeleted,
   385                          1L,
   386                          "Correct number of items are deleted");
   388             ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "delete");
   390             // Bulk insert
   391             mResolver.notifyChangeList.clear();
   392             final ContentValues[] hs = { createFillerReadingListItem(),
   393                                          createFillerReadingListItem(),
   394                                          createFillerReadingListItem() };
   396             long numBulkInserted = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, hs);
   398             mAsserter.is(numBulkInserted,
   399                          3L,
   400                          "Correct number of items are bulkInserted");
   402             ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "bulkInsert");
   403         }
   405         protected void ensureOnlyChangeNotifiedStartsWith(String expectedUri, String operation) {
   406             mAsserter.is(Long.valueOf(mResolver.notifyChangeList.size()),
   407                          1L,
   408                          "Content observer was notified exactly once by " + operation);
   410             final Uri uri = mResolver.notifyChangeList.poll();
   412             mAsserter.isnot(uri,
   413                             null,
   414                             "Notification from " + operation + " was valid");
   416             mAsserter.ok(uri.toString().startsWith(expectedUri),
   417                          "Content observer was notified exactly once by " + operation,
   418                          "");
   419         }
   420     }
   422     /**
   423      * Removes all items from the DB.
   424      */
   425     private void ensureEmptyDatabase() {
   426         Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1");
   427         getWritableDatabase(uri).delete(ReadingListItems.TABLE_NAME, null, null);
   428     }
   431     private SQLiteDatabase getWritableDatabase(Uri uri) {
   432         Uri testUri = appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
   433         DelegatingTestContentProvider delegateProvider = (DelegatingTestContentProvider) mProvider;
   434         ReadingListProvider readingListProvider = (ReadingListProvider) delegateProvider.getTargetProvider();
   435         return readingListProvider.getWritableDatabaseForTesting(testUri);
   436     }
   438     /**
   439      * Checks that the values in the cursor's current row match those
   440      * in the ContentValues object.
   441      *
   442      * @param cursor over the row to be checked
   443      * @param values to be checked
   444      */
   445     private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified) {
   446         for (String column: TEST_COLUMNS) {
   447             String expected = expectedValues.getAsString(column);
   448             String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(column));
   449             mAsserter.is(actual, expected, "Item has correct " + column);
   450         }
   452         if (compareDateModified) {
   453             String expected = expectedValues.getAsString(ReadingListItems.DATE_MODIFIED);
   454             String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(ReadingListItems.DATE_MODIFIED));
   455             mAsserter.is(actual, expected, "Item has correct " + ReadingListItems.DATE_MODIFIED);
   456         }
   457     }
   459     private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues) {
   460         assertRowEqualsContentValues(cursorWithActual, expectedValues, true);
   461     }
   463     private ContentValues fillContentValues(String title, String url, String excerpt) {
   464         ContentValues values = new ContentValues();
   466         values.put(ReadingListItems.TITLE, title);
   467         values.put(ReadingListItems.URL, url);
   468         values.put(ReadingListItems.EXCERPT, excerpt);
   469         values.put(ReadingListItems.LENGTH, excerpt.length());
   471         return values;
   472     }
   474     private ContentValues createFillerReadingListItem() {
   475         Random rand = new Random();
   476         return fillContentValues("Example", "http://example.com/?num=" + rand.nextInt(), "foo bar");
   477     }
   479     private Cursor getItemById(Uri uri, long id, String[] projection) {
   480         return mProvider.query(uri, projection,
   481                                ReadingListItems._ID + " = ?",
   482                                new String[] { String.valueOf(id) },
   483                                null);
   484     }
   486     private Cursor getItemById(long id) {
   487         return getItemById(ReadingListItems.CONTENT_URI, id, null);
   488     }
   490     private Cursor getItemById(Uri uri, long id) {
   491         return getItemById(uri, id, null);
   492     }
   494     /**
   495      * Verifies that ContentProvider insertions have been tested.
   496      */
   497     private void ensureCanInsert() {
   498         if (!mContentProviderInsertTested) {
   499             mAsserter.ok(false, "ContentProvider insertions have not been tested yet.", "");
   500         }
   501     }
   503     /**
   504      * Verifies that ContentProvider updates have been tested.
   505      */
   506     private void ensureCanUpdate() {
   507         if (!mContentProviderUpdateTested) {
   508             mAsserter.ok(false, "ContentProvider updates have not been tested yet.", "");
   509         }
   510     }
   512     private long insertAnItemWithAssertion() {
   513         // We should be able to insert into the DB.
   514         ensureCanInsert();
   516         ContentValues v = createFillerReadingListItem();
   517         long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, v));
   519         assertItemExistsByID(id, "Inserted item found");
   520         return id;
   521     }
   523     private void assertItemExistsByID(Uri uri, long id, String msg) {
   524         Cursor c = getItemById(uri, id);
   525         try {
   526             mAsserter.ok(c.moveToFirst(), msg, "");
   527         } finally {
   528             c.close();
   529         }
   530     }
   532     private void assertItemExistsByID(long id, String msg) {
   533         Cursor c = getItemById(id);
   534         try {
   535             mAsserter.ok(c.moveToFirst(), msg, "");
   536         } finally {
   537             c.close();
   538         }
   539     }
   541     private void assertItemDoesNotExistByID(long id, String msg) {
   542         Cursor c = getItemById(id);
   543         try {
   544             mAsserter.ok(!c.moveToFirst(), msg, "");
   545         } finally {
   546             c.close();
   547         }
   548     }
   550     private void assertItemDoesNotExistByID(Uri uri, long id, String msg) {
   551         Cursor c = getItemById(uri, id);
   552         try {
   553             mAsserter.ok(!c.moveToFirst(), msg, "");
   554         } finally {
   555             c.close();
   556         }
   557     }
   558 }

mercurial