mobile/android/tests/background/junit3/src/db/TestAndroidBrowserHistoryRepository.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 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 package org.mozilla.gecko.background.db;
     6 import java.util.ArrayList;
     8 import org.json.simple.JSONObject;
     9 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
    10 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
    11 import org.mozilla.gecko.background.sync.helpers.HistoryHelpers;
    12 import org.mozilla.gecko.db.BrowserContract;
    13 import org.mozilla.gecko.sync.Utils;
    14 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
    15 import org.mozilla.gecko.sync.repositories.NullCursorException;
    16 import org.mozilla.gecko.sync.repositories.Repository;
    17 import org.mozilla.gecko.sync.repositories.RepositorySession;
    18 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryDataAccessor;
    19 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepository;
    20 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepositorySession;
    21 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepository;
    22 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
    23 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositorySession;
    24 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
    25 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
    26 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
    27 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
    28 import org.mozilla.gecko.sync.repositories.domain.Record;
    30 import android.content.ContentValues;
    31 import android.content.Context;
    32 import android.database.Cursor;
    33 import android.net.Uri;
    35 public class TestAndroidBrowserHistoryRepository extends AndroidBrowserRepositoryTestCase {
    37   @Override
    38   protected AndroidBrowserRepository getRepository() {
    40     /**
    41      * Override this chain in order to avoid our test code having to create two
    42      * sessions all the time.
    43      */
    44     return new AndroidBrowserHistoryRepository() {
    45       @Override
    46       protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
    47         AndroidBrowserHistoryRepositorySession session;
    48         session = new AndroidBrowserHistoryRepositorySession(this, context) {
    49           @Override
    50           protected synchronized void trackGUID(String guid) {
    51             System.out.println("Ignoring trackGUID call: this is a test!");
    52           }
    53         };
    54         delegate.onSessionCreated(session);
    55       }
    56     };
    57   }
    59   @Override
    60   protected AndroidBrowserRepositoryDataAccessor getDataAccessor() {
    61     return new AndroidBrowserHistoryDataAccessor(getApplicationContext());
    62   }
    64   @Override
    65   protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) {
    66     if (!(dataAccessor instanceof AndroidBrowserHistoryDataAccessor)) {
    67       throw new IllegalArgumentException("Only expecting a history data accessor.");
    68     }
    69     ((AndroidBrowserHistoryDataAccessor) dataAccessor).closeExtender();
    70   }
    72   @Override
    73   public void testFetchAll() {
    74     Record[] expected = new Record[2];
    75     expected[0] = HistoryHelpers.createHistory3();
    76     expected[1] = HistoryHelpers.createHistory2();
    77     basicFetchAllTest(expected);
    78   }
    80   /*
    81    * Test storing identical records with different guids.
    82    * For bookmarks identical is defined by the following fields
    83    * being the same: title, uri, type, parentName
    84    */
    85   @Override
    86   public void testStoreIdenticalExceptGuid() {
    87     storeIdenticalExceptGuid(HistoryHelpers.createHistory1());
    88   }
    90   @Override
    91   public void testCleanMultipleRecords() {
    92     cleanMultipleRecords(
    93         HistoryHelpers.createHistory1(),
    94         HistoryHelpers.createHistory2(),
    95         HistoryHelpers.createHistory3(),
    96         HistoryHelpers.createHistory4(),
    97         HistoryHelpers.createHistory5()
    98         );
    99   }
   101   @Override
   102   public void testGuidsSinceReturnMultipleRecords() {
   103     HistoryRecord record0 = HistoryHelpers.createHistory1();
   104     HistoryRecord record1 = HistoryHelpers.createHistory2();
   105     guidsSinceReturnMultipleRecords(record0, record1);
   106   }
   108   @Override
   109   public void testGuidsSinceReturnNoRecords() {
   110     guidsSinceReturnNoRecords(HistoryHelpers.createHistory3());
   111   }
   113   @Override
   114   public void testFetchSinceOneRecord() {
   115     fetchSinceOneRecord(HistoryHelpers.createHistory1(),
   116         HistoryHelpers.createHistory2());
   117   }
   119   @Override
   120   public void testFetchSinceReturnNoRecords() {
   121     fetchSinceReturnNoRecords(HistoryHelpers.createHistory3());
   122   }
   124   @Override
   125   public void testFetchOneRecordByGuid() {
   126     fetchOneRecordByGuid(HistoryHelpers.createHistory1(),
   127         HistoryHelpers.createHistory2());
   128   }
   130   @Override
   131   public void testFetchMultipleRecordsByGuids() {
   132     HistoryRecord record0 = HistoryHelpers.createHistory1();
   133     HistoryRecord record1 = HistoryHelpers.createHistory2();
   134     HistoryRecord record2 = HistoryHelpers.createHistory3();
   135     fetchMultipleRecordsByGuids(record0, record1, record2);
   136   }
   138   @Override
   139   public void testFetchNoRecordByGuid() {
   140     fetchNoRecordByGuid(HistoryHelpers.createHistory1());
   141   }
   143   @Override
   144   public void testWipe() {
   145     doWipe(HistoryHelpers.createHistory2(), HistoryHelpers.createHistory3());
   146   }
   148   @Override
   149   public void testStore() {
   150     basicStoreTest(HistoryHelpers.createHistory1());
   151   }
   153   @Override
   154   public void testRemoteNewerTimeStamp() {
   155     HistoryRecord local = HistoryHelpers.createHistory1();
   156     HistoryRecord remote = HistoryHelpers.createHistory2();
   157     remoteNewerTimeStamp(local, remote);
   158   }
   160   @Override
   161   public void testLocalNewerTimeStamp() {
   162     HistoryRecord local = HistoryHelpers.createHistory1();
   163     HistoryRecord remote = HistoryHelpers.createHistory2();
   164     localNewerTimeStamp(local, remote);
   165   }
   167   @Override
   168   public void testDeleteRemoteNewer() {
   169     HistoryRecord local = HistoryHelpers.createHistory1();
   170     HistoryRecord remote = HistoryHelpers.createHistory2();
   171     deleteRemoteNewer(local, remote);
   172   }
   174   @Override
   175   public void testDeleteLocalNewer() {
   176     HistoryRecord local = HistoryHelpers.createHistory1();
   177     HistoryRecord remote = HistoryHelpers.createHistory2();
   178     deleteLocalNewer(local, remote);
   179   }
   181   @Override
   182   public void testDeleteRemoteLocalNonexistent() {
   183     deleteRemoteLocalNonexistent(HistoryHelpers.createHistory2());
   184   }
   186   /**
   187    * Exists to provide access to record string logic.
   188    */
   189   protected class HelperHistorySession extends AndroidBrowserHistoryRepositorySession {
   190     public HelperHistorySession(Repository repository, Context context) {
   191       super(repository, context);
   192     }
   194     public boolean sameRecordString(HistoryRecord r1, HistoryRecord r2) {
   195       return buildRecordString(r1).equals(buildRecordString(r2));
   196     }
   197   }
   199   /**
   200    * Verifies that two history records with the same URI but different
   201    * titles will be reconciled locally.
   202    */
   203   public void testRecordStringCollisionAndEquality() {
   204     final AndroidBrowserHistoryRepository repo = new AndroidBrowserHistoryRepository();
   205     final HelperHistorySession testSession = new HelperHistorySession(repo, getApplicationContext());
   207     final long now = RepositorySession.now();
   209     final HistoryRecord record0 = new HistoryRecord(null, "history", now + 1, false);
   210     final HistoryRecord record1 = new HistoryRecord(null, "history", now + 2, false);
   211     final HistoryRecord record2 = new HistoryRecord(null, "history", now + 3, false);
   213     record0.histURI = "http://example.com/foo";
   214     record1.histURI = "http://example.com/foo";
   215     record2.histURI = "http://example.com/bar";
   216     record0.title = "Foo 0";
   217     record1.title = "Foo 1";
   218     record2.title = "Foo 2";
   220     // Ensure that two records with the same URI produce the same record string,
   221     // and two records with different URIs do not.
   222     assertTrue(testSession.sameRecordString(record0, record1));
   223     assertFalse(testSession.sameRecordString(record0, record2));
   225     // Two records are congruent if they have the same URI and their
   226     // identifiers match (which is why these all have null GUIDs).
   227     assertTrue(record0.congruentWith(record0));
   228     assertTrue(record0.congruentWith(record1));
   229     assertTrue(record1.congruentWith(record0));
   230     assertFalse(record0.congruentWith(record2));
   231     assertFalse(record1.congruentWith(record2));
   232     assertFalse(record2.congruentWith(record1));
   233     assertFalse(record2.congruentWith(record0));
   235     // None of these records are equal, because they have different titles.
   236     // (Except for being equal to themselves, of course.)
   237     assertTrue(record0.equalPayloads(record0));
   238     assertTrue(record1.equalPayloads(record1));
   239     assertTrue(record2.equalPayloads(record2));
   240     assertFalse(record0.equalPayloads(record1));
   241     assertFalse(record1.equalPayloads(record0));
   242     assertFalse(record1.equalPayloads(record2));
   243   }
   245   /*
   246    * Tests for adding some visits to a history record
   247    * and doing a fetch.
   248    */
   249   @SuppressWarnings("unchecked")
   250   public void testAddOneVisit() {
   251     final RepositorySession session = createAndBeginSession();
   253     HistoryRecord record0 = HistoryHelpers.createHistory3();
   254     performWait(storeRunnable(session, record0));
   256     // Add one visit to the count and put in a new
   257     // last visited date.
   258     ContentValues cv = new ContentValues();
   259     int visits = record0.visits.size() + 1;
   260     long newVisitTime = System.currentTimeMillis();
   261     cv.put(BrowserContract.History.VISITS, visits);
   262     cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime);
   263     final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor();
   264     dataAccessor.updateByGuid(record0.guid, cv);
   266     // Add expected visit to record for verification.
   267     JSONObject expectedVisit = new JSONObject();
   268     expectedVisit.put("date", newVisitTime * 1000);    // Microseconds.
   269     expectedVisit.put("type", 1L);
   270     record0.visits.add(expectedVisit);
   272     performWait(fetchRunnable(session, new String[] { record0.guid }, new ExpectFetchDelegate(new Record[] { record0 })));
   273     closeDataAccessor(dataAccessor);
   274   }
   276   @SuppressWarnings("unchecked")
   277   public void testAddMultipleVisits() {
   278     final RepositorySession session = createAndBeginSession();
   280     HistoryRecord record0 = HistoryHelpers.createHistory4();
   281     performWait(storeRunnable(session, record0));
   283     // Add three visits to the count and put in a new
   284     // last visited date.
   285     ContentValues cv = new ContentValues();
   286     int visits = record0.visits.size() + 3;
   287     long newVisitTime = System.currentTimeMillis();
   288     cv.put(BrowserContract.History.VISITS, visits);
   289     cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime);
   290     final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor();
   291     dataAccessor.updateByGuid(record0.guid, cv);
   293     // Now shift to microsecond timing for visits.
   294     long newMicroVisitTime = newVisitTime * 1000;
   296     // Add expected visits to record for verification
   297     JSONObject expectedVisit = new JSONObject();
   298     expectedVisit.put("date", newMicroVisitTime);
   299     expectedVisit.put("type", 1L);
   300     record0.visits.add(expectedVisit);
   301     expectedVisit = new JSONObject();
   302     expectedVisit.put("date", newMicroVisitTime - 1000);
   303     expectedVisit.put("type", 1L);
   304     record0.visits.add(expectedVisit);
   305     expectedVisit = new JSONObject();
   306     expectedVisit.put("date", newMicroVisitTime - 2000);
   307     expectedVisit.put("type", 1L);
   308     record0.visits.add(expectedVisit);
   310     ExpectFetchDelegate delegate = new ExpectFetchDelegate(new Record[] { record0 });
   311     performWait(fetchRunnable(session, new String[] { record0.guid }, delegate));
   313     Record fetched = delegate.records.get(0);
   314     assertTrue(record0.equalPayloads(fetched));
   315     closeDataAccessor(dataAccessor);
   316   }
   318   public void testInvalidHistoryItemIsSkipped() throws NullCursorException {
   319     final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
   320     final AndroidBrowserRepositoryDataAccessor dbHelper = session.getDBHelper();
   322     final long now = System.currentTimeMillis();
   323     final HistoryRecord emptyURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
   324     final HistoryRecord noVisits = new HistoryRecord(Utils.generateGuid(), "history", now, false);
   325     final HistoryRecord aboutURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
   327     emptyURL.fennecDateVisited = now;
   328     emptyURL.fennecVisitCount  = 1;
   329     emptyURL.histURI           = "";
   330     emptyURL.title             = "Something";
   332     noVisits.fennecDateVisited = now;
   333     noVisits.fennecVisitCount  = 0;
   334     noVisits.histURI           = "http://example.org/novisits";
   335     noVisits.title             = "Something Else";
   337     aboutURL.fennecDateVisited = now;
   338     aboutURL.fennecVisitCount  = 1;
   339     aboutURL.histURI           = "about:home";
   340     aboutURL.title             = "Fennec Home";
   342     Uri one = dbHelper.insert(emptyURL);
   343     Uri two = dbHelper.insert(noVisits);
   344     Uri tre = dbHelper.insert(aboutURL);
   345     assertNotNull(one);
   346     assertNotNull(two);
   347     assertNotNull(tre);
   349     // The records are in the DB.
   350     final Cursor all = dbHelper.fetchAll();
   351     assertEquals(3, all.getCount());
   352     all.close();
   354     // But aren't returned by fetching.
   355     performWait(fetchAllRunnable(session, new Record[] {}));
   357     // And we'd ignore about:home if we downloaded it.
   358     assertTrue(session.shouldIgnore(aboutURL));
   360     session.abort();
   361   }
   363   public void testSqlInjectPurgeDelete() {
   364     // Some setup.
   365     RepositorySession session = createAndBeginSession();
   366     final AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
   368     try {
   369       ContentValues cv = new ContentValues();
   370       cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
   372       // Create and insert 2 history entries, 2nd one is evil (attempts injection).
   373       HistoryRecord h1 = HistoryHelpers.createHistory1();
   374       HistoryRecord h2 = HistoryHelpers.createHistory2();
   375       h2.guid = "' or '1'='1";
   377       db.insert(h1);
   378       db.insert(h2);
   380       // Test 1 - updateByGuid() handles evil history entries correctly.
   381       db.updateByGuid(h2.guid, cv);
   383       // Query history table.
   384       Cursor cur = getAllHistory();
   385       int numHistory = cur.getCount();
   387       // Ensure only the evil history entry is marked for deletion.
   388       try {
   389         cur.moveToFirst();
   390         while (!cur.isAfterLast()) {
   391           String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
   392           boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
   394           if (guid.equals(h2.guid)) {
   395             assertTrue(deleted);
   396           } else {
   397             assertFalse(deleted);
   398           }
   399           cur.moveToNext();
   400         }
   401       } finally {
   402         cur.close();
   403       }
   405       // Test 2 - Ensure purgeDelete()'s call to delete() deletes only 1 record.
   406       try {
   407         db.purgeDeleted();
   408       } catch (NullCursorException e) {
   409         e.printStackTrace();
   410       }
   412       cur = getAllHistory();
   413       int numHistoryAfterDeletion = cur.getCount();
   415       // Ensure we have only 1 deleted row.
   416       assertEquals(numHistoryAfterDeletion, numHistory - 1);
   418       // Ensure only the evil history is deleted.
   419       try {
   420         cur.moveToFirst();
   421         while (!cur.isAfterLast()) {
   422           String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
   423           boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
   425           if (guid.equals(h2.guid)) {
   426             fail("Evil guid was not deleted!");
   427           } else {
   428             assertFalse(deleted);
   429           }
   430           cur.moveToNext();
   431         }
   432       } finally {
   433         cur.close();
   434       }
   435     } finally {
   436       closeDataAccessor(db);
   437       session.abort();
   438     }
   439   }
   441   protected Cursor getAllHistory() {
   442     Context context = getApplicationContext();
   443     Cursor cur = context.getContentResolver().query(BrowserContractHelpers.HISTORY_CONTENT_URI,
   444         BrowserContractHelpers.HistoryColumns, null, null, null);
   445     return cur;
   446   }
   448   public void testDataAccessorBulkInsert() throws NullCursorException {
   449     final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
   450     AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper();
   452     ArrayList<HistoryRecord> records = new ArrayList<HistoryRecord>();
   453     records.add(HistoryHelpers.createHistory1());
   454     records.add(HistoryHelpers.createHistory2());
   455     records.add(HistoryHelpers.createHistory3());
   456     db.bulkInsert(records);
   458     performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(records.toArray(new Record[records.size()]))));
   459     session.abort();
   460   }
   462   public void testDataExtenderIsClosedBeforeBegin() {
   463     // Create a session but don't begin() it.
   464     final AndroidBrowserRepositorySession session = (AndroidBrowserRepositorySession) createSession();
   465     AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper();
   467     // Confirm dataExtender is closed before beginning session.
   468     assertTrue(db.getHistoryDataExtender().isClosed());
   469   }
   471   public void testDataExtenderIsClosedAfterFinish() throws InactiveSessionException {
   472     final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
   473     AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper();
   475     // Perform an action that opens the dataExtender.
   476     HistoryRecord h1 = HistoryHelpers.createHistory1();
   477     db.insert(h1);
   478     assertFalse(db.getHistoryDataExtender().isClosed());
   480     // Check dataExtender is closed upon finish.
   481     performWait(finishRunnable(session, new ExpectFinishDelegate()));
   482     assertTrue(db.getHistoryDataExtender().isClosed());
   483   }
   485   public void testDataExtenderIsClosedAfterAbort() throws InactiveSessionException {
   486     final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
   487     AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper();
   489     // Perform an action that opens the dataExtender.
   490     HistoryRecord h1 = HistoryHelpers.createHistory1();
   491     db.insert(h1);
   492     assertFalse(db.getHistoryDataExtender().isClosed());
   494     // Check dataExtender is closed upon abort.
   495     session.abort();
   496     assertTrue(db.getHistoryDataExtender().isClosed());
   497   }
   498 }

mercurial