michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0:
michael@0: package org.mozilla.gecko.tests;
michael@0:
michael@0: import java.util.HashSet;
michael@0: import java.util.Random;
michael@0: import java.util.concurrent.Callable;
michael@0:
michael@0: import org.mozilla.gecko.db.BrowserContract;
michael@0: import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
michael@0: import org.mozilla.gecko.db.ReadingListProvider;
michael@0:
michael@0: import android.content.ContentProvider;
michael@0: import android.content.ContentUris;
michael@0: import android.content.ContentValues;
michael@0: import android.database.Cursor;
michael@0: import android.database.sqlite.SQLiteDatabase;
michael@0: import android.net.Uri;
michael@0:
michael@0: public class testReadingListProvider extends ContentProviderTest {
michael@0:
michael@0: private static final String DB_NAME = "browser.db";
michael@0:
michael@0: // List of tests to be run sorted by dependency.
michael@0: private final TestCase[] TESTS_TO_RUN = { new TestInsertItems(),
michael@0: new TestDeleteItems(),
michael@0: new TestUpdateItems(),
michael@0: new TestBatchOperations(),
michael@0: new TestBrowserProviderNotifications() };
michael@0:
michael@0: // Columns used to test for item equivalence.
michael@0: final String[] TEST_COLUMNS = { ReadingListItems.TITLE,
michael@0: ReadingListItems.URL,
michael@0: ReadingListItems.EXCERPT,
michael@0: ReadingListItems.LENGTH,
michael@0: ReadingListItems.DATE_CREATED };
michael@0:
michael@0: // Indicates that insertions have been tested. ContentProvider.insert
michael@0: // has been proven to work.
michael@0: private boolean mContentProviderInsertTested = false;
michael@0:
michael@0: // Indicates that updates have been tested. ContentProvider.update
michael@0: // has been proven to work.
michael@0: private boolean mContentProviderUpdateTested = false;
michael@0:
michael@0: /**
michael@0: * Factory function that makes new ReadingListProvider instances.
michael@0: *
michael@0: * We want a fresh provider each test, so this should be invoked in
michael@0: * setUp
before each individual test.
michael@0: */
michael@0: private static Callable sProviderFactory = new Callable() {
michael@0: @Override
michael@0: public ContentProvider call() {
michael@0: return new ReadingListProvider();
michael@0: }
michael@0: };
michael@0:
michael@0: @Override
michael@0: public void setUp() throws Exception {
michael@0: super.setUp(sProviderFactory, BrowserContract.READING_LIST_AUTHORITY, DB_NAME);
michael@0: for (TestCase test: TESTS_TO_RUN) {
michael@0: mTests.add(test);
michael@0: }
michael@0: }
michael@0:
michael@0: public void testReadingListProviderTests() throws Exception {
michael@0: for (Runnable test : mTests) {
michael@0: setTestName(test.getClass().getSimpleName());
michael@0: ensureEmptyDatabase();
michael@0: test.run();
michael@0: }
michael@0:
michael@0: // Ensure browser initialization is complete before completing test,
michael@0: // so that the minidumps directory is consistently created.
michael@0: blockForGeckoReady();
michael@0: }
michael@0:
michael@0: /**
michael@0: * Verify that we can insert a reading list item into the DB.
michael@0: */
michael@0: private class TestInsertItems extends TestCase {
michael@0: @Override
michael@0: public void test() throws Exception {
michael@0: ContentValues b = createFillerReadingListItem();
michael@0: long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
michael@0: Cursor c = getItemById(id);
michael@0:
michael@0: try {
michael@0: mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
michael@0: assertRowEqualsContentValues(c, b);
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0:
michael@0: testInsertWithNullCol(ReadingListItems.GUID);
michael@0: mContentProviderInsertTested = true;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Test that insertion fails when a required column
michael@0: * is null.
michael@0: */
michael@0: private void testInsertWithNullCol(String colName) {
michael@0: ContentValues b = createFillerReadingListItem();
michael@0: b.putNull(colName);
michael@0:
michael@0: try {
michael@0: ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
michael@0: // If we get to here, the flawed insertion succeeded. Fail the test.
michael@0: mAsserter.ok(false, "Insertion did not succeed with " + colName + " == null", "");
michael@0: } catch (NullPointerException e) {
michael@0: // Indicates test was successful.
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Verify that we can remove a reading list item from the DB.
michael@0: */
michael@0: private class TestDeleteItems extends TestCase {
michael@0:
michael@0: @Override
michael@0: public void test() throws Exception {
michael@0: long id = insertAnItemWithAssertion();
michael@0: // Test that the item is only marked as deleted and
michael@0: // not removed from the database.
michael@0: testNonFirefoxSyncDelete(id);
michael@0:
michael@0: // Test that the item is removed from the database.
michael@0: testFirefoxSyncDelete(id);
michael@0:
michael@0: id = insertAnItemWithAssertion();
michael@0: // Test that deleting works with only a URI.
michael@0: testDeleteWithItemURI(id);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Delete an item with PARAM_IS_SYNC unset and verify that item was only marked
michael@0: * as deleted and not actually removed from the database. Also verify that the item
michael@0: * marked as deleted doesn't show up in a query.
michael@0: *
michael@0: * @param id of the item to be deleted
michael@0: */
michael@0: private void testNonFirefoxSyncDelete(long id) {
michael@0: final int deleted = mProvider.delete(ReadingListItems.CONTENT_URI,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) });
michael@0:
michael@0: mAsserter.is(deleted, 1, "Inserted item was deleted");
michael@0:
michael@0: // PARAM_SHOW_DELETED in the URI allows items marked as deleted to be
michael@0: // included in the query.
michael@0: Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
michael@0: assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
michael@0:
michael@0: // Test that the 'deleted' item does not show up in a query when PARAM_SHOW_DELETED
michael@0: // is not specified in the URI.
michael@0: assertItemDoesNotExistByID(id, "Inserted item can't be found after deletion");
michael@0: }
michael@0:
michael@0: /**
michael@0: * Delete an item with PARAM_IS_SYNC=1 and verify that item
michael@0: * was actually removed from the database.
michael@0: *
michael@0: * @param id of the item to be deleted
michael@0: */
michael@0: private void testFirefoxSyncDelete(long id) {
michael@0: final int deleted = mProvider.delete(appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) });
michael@0:
michael@0: mAsserter.is(deleted, 1, "Inserted item was deleted");
michael@0:
michael@0: Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
michael@0: assertItemDoesNotExistByID(uri, id, "Inserted item is now actually deleted");
michael@0: }
michael@0:
michael@0: /**
michael@0: * Delete an item with its URI and verify that the item
michael@0: * was actually removed from the database.
michael@0: *
michael@0: * @param id of the item to be deleted
michael@0: */
michael@0: private void testDeleteWithItemURI(long id) {
michael@0: final int deleted = mProvider.delete(ContentUris.withAppendedId(ReadingListItems.CONTENT_URI, id), null, null);
michael@0: mAsserter.is(deleted, 1, "Inserted item was deleted using URI with id");
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Verify that we can update reading list items.
michael@0: */
michael@0: private class TestUpdateItems extends TestCase {
michael@0:
michael@0: @Override
michael@0: public void test() throws Exception {
michael@0: // We should be able to insert into the DB.
michael@0: ensureCanInsert();
michael@0:
michael@0: ContentValues original = createFillerReadingListItem();
michael@0: long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, original));
michael@0: int updated = 0;
michael@0: Long originalDateCreated = null;
michael@0: Long originalDateModified = null;
michael@0: ContentValues updates = new ContentValues();
michael@0: Cursor c = getItemById(id);
michael@0: try {
michael@0: mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
michael@0:
michael@0: originalDateCreated = c.getLong(c.getColumnIndex(ReadingListItems.DATE_CREATED));
michael@0: originalDateModified = c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED));
michael@0:
michael@0: updates.put(ReadingListItems.TITLE, original.getAsString(ReadingListItems.TITLE) + "CHANGED");
michael@0: updates.put(ReadingListItems.URL, original.getAsString(ReadingListItems.URL) + "/more/stuff");
michael@0: updates.put(ReadingListItems.EXCERPT, original.getAsString(ReadingListItems.EXCERPT) + "CHANGED");
michael@0:
michael@0: updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) });
michael@0:
michael@0: mAsserter.is(updated, 1, "Inserted item was updated");
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0:
michael@0: // Name change for clarity. These values will be compared with the
michael@0: // current cursor row.
michael@0: ContentValues expectedValues = updates;
michael@0: c = getItemById(id);
michael@0: try {
michael@0: mAsserter.ok(c.moveToFirst(), "Updated item found", "");
michael@0: mAsserter.isnot(c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED)),
michael@0: originalDateModified,
michael@0: "Date modified should have changed");
michael@0:
michael@0: // DATE_CREATED and LENGTH should equal old values since they weren't updated.
michael@0: expectedValues.put(ReadingListItems.DATE_CREATED, originalDateCreated);
michael@0: expectedValues.put(ReadingListItems.LENGTH, original.getAsString(ReadingListItems.LENGTH));
michael@0: assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false);
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0:
michael@0: // Test that updates on an item that doesn't exist does not modify any rows.
michael@0: testUpdateWithInvalidID();
michael@0:
michael@0: // Test that update fails when a GUID is null.
michael@0: testUpdateWithNullCol(id, ReadingListItems.GUID);
michael@0:
michael@0: mContentProviderUpdateTested = true;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Test that updates on an item that doesn't exist does
michael@0: * not modify any rows.
michael@0: *
michael@0: * @param id of the item to be deleted
michael@0: */
michael@0: private void testUpdateWithInvalidID() {
michael@0: ensureEmptyDatabase();
michael@0: final ContentValues b = createFillerReadingListItem();
michael@0: final long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
michael@0: final long INVALID_ID = id + 1;
michael@0: final ContentValues updates = new ContentValues();
michael@0: updates.put(ReadingListItems.TITLE, b.getAsString(ReadingListItems.TITLE) + "CHANGED");
michael@0: final int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(INVALID_ID) });
michael@0: mAsserter.is(updated, 0, "Should not be able to update item with an invalid GUID");
michael@0: }
michael@0:
michael@0: /**
michael@0: * Test that update fails when a required column is null.
michael@0: */
michael@0: private int testUpdateWithNullCol(long id, String colName) {
michael@0: ContentValues updates = new ContentValues();
michael@0: updates.putNull(colName);
michael@0:
michael@0: int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) });
michael@0:
michael@0: mAsserter.is(updated, 0, "Should not be able to update item with " + colName + " == null ");
michael@0: return updated;
michael@0: }
michael@0: }
michael@0:
michael@0: private class TestBatchOperations extends TestCase {
michael@0: private static final int ITEM_COUNT = 10;
michael@0:
michael@0: /**
michael@0: * Insert a bunch of items into the DB with the bulkInsert
michael@0: * method and verify that they are there.
michael@0: */
michael@0: private void testBulkInsert() {
michael@0: ensureEmptyDatabase();
michael@0: final ContentValues allVals[] = new ContentValues[ITEM_COUNT];
michael@0: final HashSet urls = new HashSet();
michael@0: for (int i = 0; i < ITEM_COUNT; i++) {
michael@0: final String url = "http://www.test.org/" + i;
michael@0: allVals[i] = new ContentValues();
michael@0: allVals[i].put(ReadingListItems.TITLE, "Test" + i);
michael@0: allVals[i].put(ReadingListItems.URL, url);
michael@0: allVals[i].put(ReadingListItems.EXCERPT, "EXCERPT" + i);
michael@0: allVals[i].put(ReadingListItems.LENGTH, i);
michael@0: urls.add(url);
michael@0: }
michael@0:
michael@0: int inserts = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, allVals);
michael@0: mAsserter.is(inserts, ITEM_COUNT, "Excepted number of inserts matches");
michael@0:
michael@0: Cursor c = mProvider.query(ReadingListItems.CONTENT_URI, null,
michael@0: null,
michael@0: null,
michael@0: null);
michael@0: try {
michael@0: while (c.moveToNext()) {
michael@0: final String url = c.getString(c.getColumnIndex(ReadingListItems.URL));
michael@0: mAsserter.ok(urls.contains(url), "Bulk inserted item with url == " + url + " was found in the DB", "");
michael@0: // We should only be seeing each item once. Remove from set to prevent dups.
michael@0: urls.remove(url);
michael@0: }
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0: }
michael@0:
michael@0: @Override
michael@0: public void test() {
michael@0: testBulkInsert();
michael@0: }
michael@0: }
michael@0:
michael@0: /*
michael@0: * Verify that insert, update, delete, and bulkInsert operations
michael@0: * notify the ambient content resolver. Each operation calls the
michael@0: * content resolver notifyChange method synchronously, so it is
michael@0: * okay to test sequentially.
michael@0: */
michael@0: private class TestBrowserProviderNotifications extends TestCase {
michael@0:
michael@0: @Override
michael@0: public void test() {
michael@0: // We should be able to insert into the DB.
michael@0: ensureCanInsert();
michael@0: // We should be able to update the DB.
michael@0: ensureCanUpdate();
michael@0:
michael@0: final String CONTENT_URI = ReadingListItems.CONTENT_URI.toString();
michael@0:
michael@0: mResolver.notifyChangeList.clear();
michael@0:
michael@0: // Insert
michael@0: final ContentValues h = createFillerReadingListItem();
michael@0: long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, h));
michael@0:
michael@0: mAsserter.isnot(id,
michael@0: -1L,
michael@0: "Inserted item has valid id");
michael@0:
michael@0: ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "insert");
michael@0:
michael@0: // Update
michael@0: mResolver.notifyChangeList.clear();
michael@0: h.put(ReadingListItems.TITLE, "http://newexample.com");
michael@0:
michael@0: long numUpdated = mProvider.update(ReadingListItems.CONTENT_URI, h,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) });
michael@0:
michael@0: mAsserter.is(numUpdated,
michael@0: 1L,
michael@0: "Correct number of items are updated");
michael@0:
michael@0: ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "update");
michael@0:
michael@0: // Delete
michael@0: mResolver.notifyChangeList.clear();
michael@0: long numDeleted = mProvider.delete(ReadingListItems.CONTENT_URI, null, null);
michael@0:
michael@0: mAsserter.is(numDeleted,
michael@0: 1L,
michael@0: "Correct number of items are deleted");
michael@0:
michael@0: ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "delete");
michael@0:
michael@0: // Bulk insert
michael@0: mResolver.notifyChangeList.clear();
michael@0: final ContentValues[] hs = { createFillerReadingListItem(),
michael@0: createFillerReadingListItem(),
michael@0: createFillerReadingListItem() };
michael@0:
michael@0: long numBulkInserted = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, hs);
michael@0:
michael@0: mAsserter.is(numBulkInserted,
michael@0: 3L,
michael@0: "Correct number of items are bulkInserted");
michael@0:
michael@0: ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "bulkInsert");
michael@0: }
michael@0:
michael@0: protected void ensureOnlyChangeNotifiedStartsWith(String expectedUri, String operation) {
michael@0: mAsserter.is(Long.valueOf(mResolver.notifyChangeList.size()),
michael@0: 1L,
michael@0: "Content observer was notified exactly once by " + operation);
michael@0:
michael@0: final Uri uri = mResolver.notifyChangeList.poll();
michael@0:
michael@0: mAsserter.isnot(uri,
michael@0: null,
michael@0: "Notification from " + operation + " was valid");
michael@0:
michael@0: mAsserter.ok(uri.toString().startsWith(expectedUri),
michael@0: "Content observer was notified exactly once by " + operation,
michael@0: "");
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Removes all items from the DB.
michael@0: */
michael@0: private void ensureEmptyDatabase() {
michael@0: Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1");
michael@0: getWritableDatabase(uri).delete(ReadingListItems.TABLE_NAME, null, null);
michael@0: }
michael@0:
michael@0:
michael@0: private SQLiteDatabase getWritableDatabase(Uri uri) {
michael@0: Uri testUri = appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
michael@0: DelegatingTestContentProvider delegateProvider = (DelegatingTestContentProvider) mProvider;
michael@0: ReadingListProvider readingListProvider = (ReadingListProvider) delegateProvider.getTargetProvider();
michael@0: return readingListProvider.getWritableDatabaseForTesting(testUri);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Checks that the values in the cursor's current row match those
michael@0: * in the ContentValues object.
michael@0: *
michael@0: * @param cursor over the row to be checked
michael@0: * @param values to be checked
michael@0: */
michael@0: private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified) {
michael@0: for (String column: TEST_COLUMNS) {
michael@0: String expected = expectedValues.getAsString(column);
michael@0: String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(column));
michael@0: mAsserter.is(actual, expected, "Item has correct " + column);
michael@0: }
michael@0:
michael@0: if (compareDateModified) {
michael@0: String expected = expectedValues.getAsString(ReadingListItems.DATE_MODIFIED);
michael@0: String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(ReadingListItems.DATE_MODIFIED));
michael@0: mAsserter.is(actual, expected, "Item has correct " + ReadingListItems.DATE_MODIFIED);
michael@0: }
michael@0: }
michael@0:
michael@0: private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues) {
michael@0: assertRowEqualsContentValues(cursorWithActual, expectedValues, true);
michael@0: }
michael@0:
michael@0: private ContentValues fillContentValues(String title, String url, String excerpt) {
michael@0: ContentValues values = new ContentValues();
michael@0:
michael@0: values.put(ReadingListItems.TITLE, title);
michael@0: values.put(ReadingListItems.URL, url);
michael@0: values.put(ReadingListItems.EXCERPT, excerpt);
michael@0: values.put(ReadingListItems.LENGTH, excerpt.length());
michael@0:
michael@0: return values;
michael@0: }
michael@0:
michael@0: private ContentValues createFillerReadingListItem() {
michael@0: Random rand = new Random();
michael@0: return fillContentValues("Example", "http://example.com/?num=" + rand.nextInt(), "foo bar");
michael@0: }
michael@0:
michael@0: private Cursor getItemById(Uri uri, long id, String[] projection) {
michael@0: return mProvider.query(uri, projection,
michael@0: ReadingListItems._ID + " = ?",
michael@0: new String[] { String.valueOf(id) },
michael@0: null);
michael@0: }
michael@0:
michael@0: private Cursor getItemById(long id) {
michael@0: return getItemById(ReadingListItems.CONTENT_URI, id, null);
michael@0: }
michael@0:
michael@0: private Cursor getItemById(Uri uri, long id) {
michael@0: return getItemById(uri, id, null);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Verifies that ContentProvider insertions have been tested.
michael@0: */
michael@0: private void ensureCanInsert() {
michael@0: if (!mContentProviderInsertTested) {
michael@0: mAsserter.ok(false, "ContentProvider insertions have not been tested yet.", "");
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Verifies that ContentProvider updates have been tested.
michael@0: */
michael@0: private void ensureCanUpdate() {
michael@0: if (!mContentProviderUpdateTested) {
michael@0: mAsserter.ok(false, "ContentProvider updates have not been tested yet.", "");
michael@0: }
michael@0: }
michael@0:
michael@0: private long insertAnItemWithAssertion() {
michael@0: // We should be able to insert into the DB.
michael@0: ensureCanInsert();
michael@0:
michael@0: ContentValues v = createFillerReadingListItem();
michael@0: long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, v));
michael@0:
michael@0: assertItemExistsByID(id, "Inserted item found");
michael@0: return id;
michael@0: }
michael@0:
michael@0: private void assertItemExistsByID(Uri uri, long id, String msg) {
michael@0: Cursor c = getItemById(uri, id);
michael@0: try {
michael@0: mAsserter.ok(c.moveToFirst(), msg, "");
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0: }
michael@0:
michael@0: private void assertItemExistsByID(long id, String msg) {
michael@0: Cursor c = getItemById(id);
michael@0: try {
michael@0: mAsserter.ok(c.moveToFirst(), msg, "");
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0: }
michael@0:
michael@0: private void assertItemDoesNotExistByID(long id, String msg) {
michael@0: Cursor c = getItemById(id);
michael@0: try {
michael@0: mAsserter.ok(!c.moveToFirst(), msg, "");
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0: }
michael@0:
michael@0: private void assertItemDoesNotExistByID(Uri uri, long id, String msg) {
michael@0: Cursor c = getItemById(uri, id);
michael@0: try {
michael@0: mAsserter.ok(!c.moveToFirst(), msg, "");
michael@0: } finally {
michael@0: c.close();
michael@0: }
michael@0: }
michael@0: }