michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: package org.mozilla.gecko.background.db; michael@0: michael@0: import java.util.concurrent.ExecutorService; michael@0: michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultBeginDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectBeginDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectBeginFailDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.SessionTestHelper; michael@0: import org.mozilla.gecko.background.testhelpers.WaitHelper; michael@0: import org.mozilla.gecko.db.BrowserContract; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: import org.mozilla.gecko.sync.repositories.InactiveSessionException; michael@0: import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; michael@0: import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; michael@0: import org.mozilla.gecko.sync.repositories.Repository; michael@0: import org.mozilla.gecko.sync.repositories.RepositorySession; michael@0: import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate; michael@0: import org.mozilla.gecko.sync.repositories.domain.Record; michael@0: michael@0: import android.content.ContentValues; michael@0: import android.content.Context; michael@0: michael@0: public abstract class AndroidBrowserRepositoryTestCase extends AndroidSyncTestCase { michael@0: protected static String LOG_TAG = "BrowserRepositoryTest"; michael@0: michael@0: protected static void wipe(AndroidBrowserRepositoryDataAccessor helper) { michael@0: Logger.debug(LOG_TAG, "Wiping."); michael@0: try { michael@0: helper.wipe(); michael@0: } catch (NullPointerException e) { michael@0: // This will be handled in begin, here we can just ignore michael@0: // the error if it actually occurs since this is just test michael@0: // code. We will throw a ProfileDatabaseException. This michael@0: // error shouldn't occur in the future, but results from michael@0: // trying to access content providers before Fennec has michael@0: // been run at least once. michael@0: Logger.error(LOG_TAG, "ProfileDatabaseException seen in wipe. Begin should fail"); michael@0: fail("NullPointerException in wipe."); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void setUp() { michael@0: AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); michael@0: wipe(helper); michael@0: assertTrue(WaitHelper.getTestWaiter().isIdle()); michael@0: closeDataAccessor(helper); michael@0: } michael@0: michael@0: public void tearDown() { michael@0: assertTrue(WaitHelper.getTestWaiter().isIdle()); michael@0: } michael@0: michael@0: protected RepositorySession createSession() { michael@0: return SessionTestHelper.createSession( michael@0: getApplicationContext(), michael@0: getRepository()); michael@0: } michael@0: michael@0: protected RepositorySession createAndBeginSession() { michael@0: return SessionTestHelper.createAndBeginSession( michael@0: getApplicationContext(), michael@0: getRepository()); michael@0: } michael@0: michael@0: protected static void dispose(RepositorySession session) { michael@0: if (session != null) { michael@0: session.abort(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored. michael@0: */ michael@0: public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) { michael@0: return new ExpectFetchDelegate(expected); michael@0: } michael@0: michael@0: /** michael@0: * Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored. michael@0: */ michael@0: public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) { michael@0: return new ExpectGuidsSinceDelegate(expected); michael@0: } michael@0: michael@0: /** michael@0: * Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any). michael@0: */ michael@0: public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() { michael@0: return new ExpectGuidsSinceDelegate(new String[] {}); michael@0: } michael@0: michael@0: /** michael@0: * Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored. michael@0: */ michael@0: public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) { michael@0: return new ExpectFetchSinceDelegate(timestamp, expected); michael@0: } michael@0: michael@0: public static Runnable storeRunnable(final RepositorySession session, final Record record, final DefaultStoreDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.setStoreDelegate(delegate); michael@0: try { michael@0: session.store(record); michael@0: session.storeDone(); michael@0: } catch (NoStoreDelegateException e) { michael@0: fail("NoStoreDelegateException should not occur."); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static Runnable storeRunnable(final RepositorySession session, final Record record) { michael@0: return storeRunnable(session, record, new ExpectStoredDelegate(record.guid)); michael@0: } michael@0: michael@0: public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records, final DefaultStoreDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.setStoreDelegate(delegate); michael@0: try { michael@0: for (Record record : records) { michael@0: session.store(record); michael@0: } michael@0: session.storeDone(); michael@0: } catch (NoStoreDelegateException e) { michael@0: fail("NoStoreDelegateException should not occur."); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records) { michael@0: return storeManyRunnable(session, records, new ExpectManyStoredDelegate(records)); michael@0: } michael@0: michael@0: /** michael@0: * Store a record and don't expect a store callback until we're done. michael@0: * michael@0: * @param session michael@0: * @param record michael@0: * @return Runnable. michael@0: */ michael@0: public static Runnable quietStoreRunnable(final RepositorySession session, final Record record) { michael@0: return storeRunnable(session, record, new ExpectStoreCompletedDelegate()); michael@0: } michael@0: michael@0: public static Runnable beginRunnable(final RepositorySession session, final DefaultBeginDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: session.begin(delegate); michael@0: } catch (InvalidSessionTransitionException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static Runnable finishRunnable(final RepositorySession session, final DefaultFinishDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: session.finish(delegate); michael@0: } catch (InactiveSessionException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static Runnable fetchAllRunnable(final RepositorySession session, final ExpectFetchDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.fetchAll(delegate); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) { michael@0: return fetchAllRunnable(session, preparedExpectFetchDelegate(expectedRecords)); michael@0: } michael@0: michael@0: public Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.guidsSince(timestamp, preparedExpectGuidsSinceDelegate(expected)); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.fetchSince(timestamp, preparedExpectFetchSinceDelegate(timestamp, expected)); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final DefaultFetchDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: session.fetch(guids, delegate); michael@0: } catch (InactiveSessionException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: public Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) { michael@0: return fetchRunnable(session, guids, preparedExpectFetchDelegate(expected)); michael@0: } michael@0: michael@0: public static Runnable cleanRunnable(final Repository repository, final boolean success, final Context context, final DefaultCleanDelegate delegate) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: repository.clean(success, delegate, context); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: protected abstract Repository getRepository(); michael@0: protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor(); michael@0: michael@0: protected static void doStore(RepositorySession session, Record[] records) { michael@0: performWait(storeManyRunnable(session, records)); michael@0: } michael@0: michael@0: // Tests to implement michael@0: public abstract void testFetchAll(); michael@0: public abstract void testGuidsSinceReturnMultipleRecords(); michael@0: public abstract void testGuidsSinceReturnNoRecords(); michael@0: public abstract void testFetchSinceOneRecord(); michael@0: public abstract void testFetchSinceReturnNoRecords(); michael@0: public abstract void testFetchOneRecordByGuid(); michael@0: public abstract void testFetchMultipleRecordsByGuids(); michael@0: public abstract void testFetchNoRecordByGuid(); michael@0: public abstract void testWipe(); michael@0: public abstract void testStore(); michael@0: public abstract void testRemoteNewerTimeStamp(); michael@0: public abstract void testLocalNewerTimeStamp(); michael@0: public abstract void testDeleteRemoteNewer(); michael@0: public abstract void testDeleteLocalNewer(); michael@0: public abstract void testDeleteRemoteLocalNonexistent(); michael@0: public abstract void testStoreIdenticalExceptGuid(); michael@0: public abstract void testCleanMultipleRecords(); michael@0: michael@0: michael@0: /* michael@0: * Test abstractions michael@0: */ michael@0: protected void basicStoreTest(Record record) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: performWait(storeRunnable(session, record)); michael@0: } michael@0: michael@0: protected void basicFetchAllTest(Record[] expected) { michael@0: Logger.debug("rnewman", "Starting testFetchAll."); michael@0: RepositorySession session = createAndBeginSession(); michael@0: Logger.debug("rnewman", "Prepared."); michael@0: michael@0: AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); michael@0: helper.dumpDB(); michael@0: performWait(storeManyRunnable(session, expected)); michael@0: michael@0: helper.dumpDB(); michael@0: performWait(fetchAllRunnable(session, expected)); michael@0: michael@0: closeDataAccessor(helper); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Tests for clean michael@0: */ michael@0: // Input: 4 records; 2 which are to be cleaned, 2 which should remain after the clean michael@0: protected void cleanMultipleRecords(Record delete0, Record delete1, Record keep0, Record keep1, Record keep2) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: doStore(session, new Record[] { michael@0: delete0, delete1, keep0, keep1, keep2 michael@0: }); michael@0: michael@0: // Force two records to appear deleted. michael@0: AndroidBrowserRepositoryDataAccessor db = getDataAccessor(); michael@0: ContentValues cv = new ContentValues(); michael@0: cv.put(BrowserContract.SyncColumns.IS_DELETED, 1); michael@0: db.updateByGuid(delete0.guid, cv); michael@0: db.updateByGuid(delete1.guid, cv); michael@0: michael@0: final DefaultCleanDelegate delegate = new DefaultCleanDelegate() { michael@0: public void onCleaned(Repository repo) { michael@0: performNotify(); michael@0: } michael@0: }; michael@0: michael@0: final Runnable cleanRunnable = cleanRunnable( michael@0: getRepository(), michael@0: true, michael@0: getApplicationContext(), michael@0: delegate); michael@0: michael@0: performWait(cleanRunnable); michael@0: performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[] { keep0, keep1, keep2}))); michael@0: closeDataAccessor(db); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Tests for guidsSince michael@0: */ michael@0: protected void guidsSinceReturnMultipleRecords(Record record0, Record record1) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: long timestamp = System.currentTimeMillis(); michael@0: michael@0: String[] expected = new String[2]; michael@0: expected[0] = record0.guid; michael@0: expected[1] = record1.guid; michael@0: michael@0: Logger.debug(getName(), "Storing two records..."); michael@0: performWait(storeManyRunnable(session, new Record[] { record0, record1 })); michael@0: Logger.debug(getName(), "Getting guids since " + timestamp + "; expecting " + expected.length); michael@0: performWait(guidsSinceRunnable(session, timestamp, expected)); michael@0: dispose(session); michael@0: } michael@0: michael@0: protected void guidsSinceReturnNoRecords(Record record0) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: // Store 1 record in the past. michael@0: performWait(storeRunnable(session, record0)); michael@0: michael@0: String[] expected = {}; michael@0: performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected)); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Tests for fetchSince michael@0: */ michael@0: protected void fetchSinceOneRecord(Record record0, Record record1) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: performWait(storeRunnable(session, record0)); michael@0: long timestamp = System.currentTimeMillis(); michael@0: Logger.debug("fetchSinceOneRecord", "Entering synchronized section. Timestamp " + timestamp); michael@0: synchronized(this) { michael@0: try { michael@0: wait(1000); michael@0: } catch (InterruptedException e) { michael@0: Logger.warn("fetchSinceOneRecord", "Interrupted.", e); michael@0: } michael@0: } michael@0: Logger.debug("fetchSinceOneRecord", "Storing."); michael@0: performWait(storeRunnable(session, record1)); michael@0: michael@0: Logger.debug("fetchSinceOneRecord", "Fetching record 1."); michael@0: String[] expectedOne = new String[] { record1.guid }; michael@0: performWait(fetchSinceRunnable(session, timestamp + 10, expectedOne)); michael@0: michael@0: Logger.debug("fetchSinceOneRecord", "Fetching both, relying on inclusiveness."); michael@0: String[] expectedBoth = new String[] { record0.guid, record1.guid }; michael@0: performWait(fetchSinceRunnable(session, timestamp - 3000, expectedBoth)); michael@0: michael@0: Logger.debug("fetchSinceOneRecord", "Done."); michael@0: dispose(session); michael@0: } michael@0: michael@0: protected void fetchSinceReturnNoRecords(Record record) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: performWait(storeRunnable(session, record)); michael@0: michael@0: long timestamp = System.currentTimeMillis(); michael@0: michael@0: performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {})); michael@0: dispose(session); michael@0: } michael@0: michael@0: protected void fetchOneRecordByGuid(Record record0, Record record1) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: Record[] store = new Record[] { record0, record1 }; michael@0: performWait(storeManyRunnable(session, store)); michael@0: michael@0: String[] guids = new String[] { record0.guid }; michael@0: Record[] expected = new Record[] { record0 }; michael@0: performWait(fetchRunnable(session, guids, expected)); michael@0: dispose(session); michael@0: } michael@0: michael@0: protected void fetchMultipleRecordsByGuids(Record record0, michael@0: Record record1, Record record2) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: Record[] store = new Record[] { record0, record1, record2 }; michael@0: performWait(storeManyRunnable(session, store)); michael@0: michael@0: String[] guids = new String[] { record0.guid, record2.guid }; michael@0: Record[] expected = new Record[] { record0, record2 }; michael@0: performWait(fetchRunnable(session, guids, expected)); michael@0: dispose(session); michael@0: } michael@0: michael@0: protected void fetchNoRecordByGuid(Record record) { michael@0: RepositorySession session = createAndBeginSession(); michael@0: michael@0: performWait(storeRunnable(session, record)); michael@0: performWait(fetchRunnable(session, michael@0: new String[] { Utils.generateGuid() }, michael@0: new Record[] {})); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Test wipe michael@0: */ michael@0: protected void doWipe(final Record record0, final Record record1) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: final Runnable run = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.wipe(new RepositorySessionWipeDelegate() { michael@0: public void onWipeSucceeded() { michael@0: performNotify(); michael@0: } michael@0: public void onWipeFailed(Exception ex) { michael@0: fail("wipe should have succeeded"); michael@0: performNotify(); michael@0: } michael@0: @Override michael@0: public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) { michael@0: final RepositorySessionWipeDelegate self = this; michael@0: return new RepositorySessionWipeDelegate() { michael@0: michael@0: @Override michael@0: public void onWipeSucceeded() { michael@0: new Thread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: self.onWipeSucceeded(); michael@0: }}).start(); michael@0: } michael@0: michael@0: @Override michael@0: public void onWipeFailed(final Exception ex) { michael@0: new Thread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: self.onWipeFailed(ex); michael@0: }}).start(); michael@0: } michael@0: michael@0: @Override michael@0: public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService newExecutor) { michael@0: if (newExecutor == executor) { michael@0: return this; michael@0: } michael@0: throw new IllegalArgumentException("Can't re-defer this delegate."); michael@0: } michael@0: }; michael@0: } michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: // Store 2 records. michael@0: Record[] records = new Record[] { record0, record1 }; michael@0: performWait(storeManyRunnable(session, records)); michael@0: performWait(fetchAllRunnable(session, records)); michael@0: michael@0: // Wipe. michael@0: performWait(run); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * TODO adding or subtracting from lastModified timestamps does NOTHING michael@0: * since it gets overwritten when we store stuff. See other tests michael@0: * for ways to do this properly. michael@0: */ michael@0: michael@0: /* michael@0: * Record being stored has newer timestamp than existing local record, local michael@0: * record has not been modified since last sync. michael@0: */ michael@0: protected void remoteNewerTimeStamp(Record local, Record remote) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: michael@0: // Record existing and hasn't changed since before lastSync. michael@0: // Automatically will be assigned lastModified = current time. michael@0: performWait(storeRunnable(session, local)); michael@0: michael@0: remote.guid = local.guid; michael@0: michael@0: // Get the timestamp and make remote newer than it michael@0: ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); michael@0: performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); michael@0: remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; michael@0: performWait(storeRunnable(session, remote)); michael@0: michael@0: Record[] expected = new Record[] { remote }; michael@0: ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected); michael@0: performWait(fetchAllRunnable(session, delegate)); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Local record has a newer timestamp than the record being stored. For now, michael@0: * we just take newer (local) record) michael@0: */ michael@0: protected void localNewerTimeStamp(Record local, Record remote) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: michael@0: performWait(storeRunnable(session, local)); michael@0: michael@0: remote.guid = local.guid; michael@0: michael@0: // Get the timestamp and make remote older than it michael@0: ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); michael@0: performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); michael@0: remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; michael@0: performWait(storeRunnable(session, remote)); michael@0: michael@0: // Do a fetch and make sure that we get back the local record. michael@0: Record[] expected = new Record[] { local }; michael@0: performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Insert a record that is marked as deleted, remote has newer timestamp michael@0: */ michael@0: protected void deleteRemoteNewer(Record local, Record remote) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: michael@0: // Record existing and hasn't changed since before lastSync. michael@0: // Automatically will be assigned lastModified = current time. michael@0: performWait(storeRunnable(session, local)); michael@0: michael@0: // Pass the same record to store, but mark it deleted and modified michael@0: // more recently michael@0: ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); michael@0: performWait(fetchRunnable(session, new String[] { local.guid }, timestampDelegate)); michael@0: remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; michael@0: remote.deleted = true; michael@0: remote.guid = local.guid; michael@0: performWait(storeRunnable(session, remote)); michael@0: michael@0: performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[]{}))); michael@0: dispose(session); michael@0: } michael@0: michael@0: // Store two records that are identical (this has different meanings based on the michael@0: // type of record) other than their guids. The record existing locally already michael@0: // should have its guid replaced (the assumption is that the record existed locally michael@0: // and then sync was enabled and this record existed on another sync'd device). michael@0: public void storeIdenticalExceptGuid(Record record0) { michael@0: Logger.debug("storeIdenticalExceptGuid", "Started."); michael@0: final RepositorySession session = createAndBeginSession(); michael@0: Logger.debug("storeIdenticalExceptGuid", "Session is " + session); michael@0: performWait(storeRunnable(session, record0)); michael@0: Logger.debug("storeIdenticalExceptGuid", "Stored record0."); michael@0: DefaultFetchDelegate timestampDelegate = getTimestampDelegate(record0.guid); michael@0: michael@0: performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate)); michael@0: Logger.debug("storeIdenticalExceptGuid", "fetchRunnable done."); michael@0: record0.lastModified = timestampDelegate.records.get(0).lastModified + 3000; michael@0: record0.guid = Utils.generateGuid(); michael@0: Logger.debug("storeIdenticalExceptGuid", "Storing modified..."); michael@0: performWait(storeRunnable(session, record0)); michael@0: Logger.debug("storeIdenticalExceptGuid", "Stored modified."); michael@0: michael@0: Record[] expected = new Record[] { record0 }; michael@0: performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); michael@0: Logger.debug("storeIdenticalExceptGuid", "Fetched all. Returning."); michael@0: dispose(session); michael@0: } michael@0: michael@0: // Special delegate so that we don't verify parenting is correct since michael@0: // at some points it won't be since parent folder hasn't been stored. michael@0: private DefaultFetchDelegate getTimestampDelegate(final String guid) { michael@0: return new DefaultFetchDelegate() { michael@0: @Override michael@0: public void onFetchCompleted(final long fetchEnd) { michael@0: assertEquals(guid, this.records.get(0).guid); michael@0: performNotify(); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: /* michael@0: * Insert a record that is marked as deleted, local has newer timestamp michael@0: * and was not marked deleted (so keep it) michael@0: */ michael@0: protected void deleteLocalNewer(Record local, Record remote) { michael@0: Logger.debug("deleteLocalNewer", "Begin."); michael@0: final RepositorySession session = createAndBeginSession(); michael@0: michael@0: Logger.debug("deleteLocalNewer", "Storing local..."); michael@0: performWait(storeRunnable(session, local)); michael@0: michael@0: // Create an older version of a record with the same GUID. michael@0: remote.guid = local.guid; michael@0: michael@0: Logger.debug("deleteLocalNewer", "Fetching..."); michael@0: michael@0: // Get the timestamp and make remote older than it michael@0: Record[] expected = new Record[] { local }; michael@0: ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(expected); michael@0: performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); michael@0: michael@0: Logger.debug("deleteLocalNewer", "Fetched."); michael@0: remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; michael@0: michael@0: Logger.debug("deleteLocalNewer", "Last modified is " + remote.lastModified); michael@0: remote.deleted = true; michael@0: Logger.debug("deleteLocalNewer", "Storing deleted..."); michael@0: performWait(quietStoreRunnable(session, remote)); // This appears to do a lot of work...?! michael@0: michael@0: // Do a fetch and make sure that we get back the first (local) record. michael@0: performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); michael@0: Logger.debug("deleteLocalNewer", "Fetched and done!"); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Insert a record that is marked as deleted, record never existed locally michael@0: */ michael@0: protected void deleteRemoteLocalNonexistent(Record remote) { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: michael@0: long timestamp = 1000000000; michael@0: michael@0: // Pass a record marked deleted to store, doesn't exist locally michael@0: remote.lastModified = timestamp; michael@0: remote.deleted = true; michael@0: performWait(quietStoreRunnable(session, remote)); michael@0: michael@0: ExpectFetchDelegate delegate = preparedExpectFetchDelegate(new Record[]{}); michael@0: performWait(fetchAllRunnable(session, delegate)); michael@0: dispose(session); michael@0: } michael@0: michael@0: /* michael@0: * Tests that don't require specific records based on type of repository. michael@0: * These tests don't need to be overriden in subclasses, they will just work. michael@0: */ michael@0: public void testCreateSessionNullContext() { michael@0: Logger.debug(LOG_TAG, "In testCreateSessionNullContext."); michael@0: Repository repo = getRepository(); michael@0: try { michael@0: repo.createSession(new DefaultSessionCreationDelegate(), null); michael@0: fail("Should throw."); michael@0: } catch (Exception ex) { michael@0: assertNotNull(ex); michael@0: } michael@0: } michael@0: michael@0: public void testStoreNullRecord() { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: try { michael@0: session.setStoreDelegate(new DefaultStoreDelegate()); michael@0: session.store(null); michael@0: fail("Should throw."); michael@0: } catch (Exception ex) { michael@0: assertNotNull(ex); michael@0: } michael@0: dispose(session); michael@0: } michael@0: michael@0: public void testFetchNoGuids() { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: performWait(fetchRunnable(session, new String[] {}, new ExpectInvalidRequestFetchDelegate())); michael@0: dispose(session); michael@0: } michael@0: michael@0: public void testFetchNullGuids() { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: performWait(fetchRunnable(session, null, new ExpectInvalidRequestFetchDelegate())); michael@0: dispose(session); michael@0: } michael@0: michael@0: public void testBeginOnNewSession() { michael@0: final RepositorySession session = createSession(); michael@0: performWait(beginRunnable(session, new ExpectBeginDelegate())); michael@0: dispose(session); michael@0: } michael@0: michael@0: public void testBeginOnRunningSession() { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: try { michael@0: session.begin(new ExpectBeginFailDelegate()); michael@0: } catch (InvalidSessionTransitionException e) { michael@0: dispose(session); michael@0: return; michael@0: } michael@0: fail("Should have caught InvalidSessionTransitionException."); michael@0: } michael@0: michael@0: public void testBeginOnFinishedSession() throws InactiveSessionException { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: performWait(finishRunnable(session, new ExpectFinishDelegate())); michael@0: try { michael@0: session.begin(new ExpectBeginFailDelegate()); michael@0: } catch (InvalidSessionTransitionException e) { michael@0: Logger.debug(getName(), "Yay! Got an exception.", e); michael@0: dispose(session); michael@0: return; michael@0: } catch (Exception e) { michael@0: Logger.debug(getName(), "Yay! Got an exception.", e); michael@0: dispose(session); michael@0: return; michael@0: } michael@0: fail("Should have caught InvalidSessionTransitionException."); michael@0: } michael@0: michael@0: public void testFinishOnFinishedSession() throws InactiveSessionException { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: performWait(finishRunnable(session, new ExpectFinishDelegate())); michael@0: try { michael@0: session.finish(new ExpectFinishFailDelegate()); michael@0: } catch (InactiveSessionException e) { michael@0: dispose(session); michael@0: return; michael@0: } michael@0: fail("Should have caught InactiveSessionException."); michael@0: } michael@0: michael@0: public void testFetchOnInactiveSession() throws InactiveSessionException { michael@0: final RepositorySession session = createSession(); michael@0: try { michael@0: session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); michael@0: } catch (InactiveSessionException e) { michael@0: // Yay. michael@0: dispose(session); michael@0: return; michael@0: }; michael@0: fail("Should have caught InactiveSessionException."); michael@0: } michael@0: michael@0: public void testFetchOnFinishedSession() { michael@0: final RepositorySession session = createAndBeginSession(); michael@0: Logger.debug(getName(), "Finishing..."); michael@0: performWait(finishRunnable(session, new ExpectFinishDelegate())); michael@0: try { michael@0: session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); michael@0: } catch (InactiveSessionException e) { michael@0: // Yay. michael@0: dispose(session); michael@0: return; michael@0: }; michael@0: fail("Should have caught InactiveSessionException."); michael@0: } michael@0: michael@0: public void testGuidsSinceOnUnstartedSession() { michael@0: final RepositorySession session = createSession(); michael@0: Runnable run = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: session.guidsSince(System.currentTimeMillis(), michael@0: new RepositorySessionGuidsSinceDelegate() { michael@0: public void onGuidsSinceSucceeded(String[] guids) { michael@0: fail("Session inactive, should fail"); michael@0: performNotify(); michael@0: } michael@0: michael@0: public void onGuidsSinceFailed(Exception ex) { michael@0: verifyInactiveException(ex); michael@0: performNotify(); michael@0: } michael@0: }); michael@0: } michael@0: }; michael@0: performWait(run); michael@0: dispose(session); michael@0: } michael@0: michael@0: private static void verifyInactiveException(Exception ex) { michael@0: if (!(ex instanceof InactiveSessionException)) { michael@0: fail("Wrong exception type"); michael@0: } michael@0: } michael@0: michael@0: protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) { michael@0: } michael@0: }