/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ package org.mozilla.android.sync.test; import java.util.HashSet; import java.util.Set; import org.mozilla.android.sync.test.helpers.ExpectFetchDelegate; import org.mozilla.android.sync.test.helpers.ExpectFetchSinceDelegate; import org.mozilla.android.sync.test.helpers.ExpectGuidsSinceDelegate; import org.mozilla.android.sync.test.helpers.ExpectStoredDelegate; import org.mozilla.android.sync.test.helpers.PasswordHelpers; import org.mozilla.android.sync.test.helpers.SessionTestHelper; import org.mozilla.android.sync.test.helpers.WaitHelper; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.repositories.InactiveSessionException; import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; import org.mozilla.gecko.sync.repositories.Repository; import org.mozilla.gecko.sync.repositories.RepositorySession; import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession; import org.mozilla.gecko.sync.repositories.android.RepoUtils; import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; import org.mozilla.gecko.sync.repositories.domain.PasswordRecord; import org.mozilla.gecko.sync.repositories.domain.Record; import android.content.ContentProviderClient; import android.content.Context; import android.database.Cursor; import android.os.RemoteException; public class TestPasswordsRepository extends AndroidSyncTestCase { private final String NEW_PASSWORD1 = "password"; private final String NEW_PASSWORD2 = "drowssap"; @Override public void setUp() { wipe(); assertTrue(WaitHelper.getTestWaiter().isIdle()); } public void testFetchAll() { RepositorySession session = createAndBeginSession(); Record[] expected = new Record[] { PasswordHelpers.createPassword1(), PasswordHelpers.createPassword2() }; performWait(storeRunnable(session, expected[0])); performWait(storeRunnable(session, expected[1])); performWait(fetchAllRunnable(session, expected)); dispose(session); } public void testGuidsSinceReturnMultipleRecords() { RepositorySession session = createAndBeginSession(); PasswordRecord record1 = PasswordHelpers.createPassword1(); PasswordRecord record2 = PasswordHelpers.createPassword2(); updatePassword(NEW_PASSWORD1, record1); long timestamp = updatePassword(NEW_PASSWORD2, record2); String[] expected = new String[] { record1.guid, record2.guid }; performWait(storeRunnable(session, record1)); performWait(storeRunnable(session, record2)); performWait(guidsSinceRunnable(session, timestamp, expected)); dispose(session); } public void testGuidsSinceReturnNoRecords() { RepositorySession session = createAndBeginSession(); // Store 1 record in the past. performWait(storeRunnable(session, PasswordHelpers.createPassword1())); String[] expected = {}; performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected)); dispose(session); } public void testFetchSinceOneRecord() { RepositorySession session = createAndBeginSession(); // Passwords fetchSince checks timePasswordChanged, not insertion time. PasswordRecord record1 = PasswordHelpers.createPassword1(); long timeModified1 = updatePassword(NEW_PASSWORD1, record1); performWait(storeRunnable(session, record1)); PasswordRecord record2 = PasswordHelpers.createPassword2(); long timeModified2 = updatePassword(NEW_PASSWORD2, record2); performWait(storeRunnable(session, record2)); String[] expectedOne = new String[] { record2.guid }; performWait(fetchSinceRunnable(session, timeModified2 - 10, expectedOne)); String[] expectedBoth = new String[] { record1.guid, record2.guid }; performWait(fetchSinceRunnable(session, timeModified1 - 10, expectedBoth)); dispose(session); } public void testFetchSinceReturnNoRecords() { RepositorySession session = createAndBeginSession(); performWait(storeRunnable(session, PasswordHelpers.createPassword2())); long timestamp = System.currentTimeMillis(); performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {})); dispose(session); } public void testFetchOneRecordByGuid() { RepositorySession session = createAndBeginSession(); Record record = PasswordHelpers.createPassword1(); performWait(storeRunnable(session, record)); performWait(storeRunnable(session, PasswordHelpers.createPassword2())); String[] guids = new String[] { record.guid }; Record[] expected = new Record[] { record }; performWait(fetchRunnable(session, guids, expected)); dispose(session); } public void testFetchMultipleRecordsByGuids() { RepositorySession session = createAndBeginSession(); PasswordRecord record1 = PasswordHelpers.createPassword1(); PasswordRecord record2 = PasswordHelpers.createPassword2(); PasswordRecord record3 = PasswordHelpers.createPassword3(); performWait(storeRunnable(session, record1)); performWait(storeRunnable(session, record2)); performWait(storeRunnable(session, record3)); String[] guids = new String[] { record1.guid, record2.guid }; Record[] expected = new Record[] { record1, record2 }; performWait(fetchRunnable(session, guids, expected)); dispose(session); } public void testFetchNoRecordByGuid() { RepositorySession session = createAndBeginSession(); Record record = PasswordHelpers.createPassword1(); performWait(storeRunnable(session, record)); performWait(fetchRunnable(session, new String[] { Utils.generateGuid() }, new Record[] {})); dispose(session); } public void testStore() { final RepositorySession session = createAndBeginSession(); performWait(storeRunnable(session, PasswordHelpers.createPassword1())); dispose(session); } public void testRemoteNewerTimeStamp() { final RepositorySession session = createAndBeginSession(); // Store updated local record. PasswordRecord local = PasswordHelpers.createPassword1(); updatePassword(NEW_PASSWORD1, local, System.currentTimeMillis() - 1000); performWait(storeRunnable(session, local)); // Sync a remote record version that is newer. PasswordRecord remote = PasswordHelpers.createPassword2(); remote.guid = local.guid; updatePassword(NEW_PASSWORD2, remote); performWait(storeRunnable(session, remote)); // Make a fetch, expecting only the newer (remote) record. performWait(fetchAllRunnable(session, new Record[] { remote })); dispose(session); } public void testLocalNewerTimeStamp() { final RepositorySession session = createAndBeginSession(); // Remote record updated before local record. PasswordRecord remote = PasswordHelpers.createPassword1(); updatePassword(NEW_PASSWORD1, remote, System.currentTimeMillis() - 1000); // Store updated local record. PasswordRecord local = PasswordHelpers.createPassword2(); updatePassword(NEW_PASSWORD2, local); performWait(storeRunnable(session, local)); // Sync a remote record version that is older. remote.guid = local.guid; performWait(storeRunnable(session, remote)); // Make a fetch, expecting only the newer (local) record. performWait(fetchAllRunnable(session, new Record[] { local })); dispose(session); } /* * Store two records that are identical except for guid. Expect to find the * remote one after reconciling. */ public void testStoreIdenticalExceptGuid() { RepositorySession session = createAndBeginSession(); PasswordRecord record = PasswordHelpers.createPassword1(); record.guid = "before1"; // Store record. performWait(storeRunnable(session, record)); // Store same record, but with different guid. record.guid = Utils.generateGuid(); performWait(storeRunnable(session, record)); performWait(fetchAllRunnable(session, new Record[] { record })); dispose(session); session = createAndBeginSession(); PasswordRecord record2 = PasswordHelpers.createPassword2(); record2.guid = "before2"; // Store record. performWait(storeRunnable(session, record2)); // Store same record, but with different guid. record2.guid = Utils.generateGuid(); performWait(storeRunnable(session, record2)); performWait(fetchAllRunnable(session, new Record[] { record, record2 })); dispose(session); } /* * Store two records that are identical except for guid when they both point * to the same site and there are multiple records for that site. Expect to * find the remote one after reconciling. */ public void testStoreIdenticalExceptGuidOnSameSite() { RepositorySession session = createAndBeginSession(); PasswordRecord record1 = PasswordHelpers.createPassword1(); record1.encryptedUsername = "original"; record1.guid = "before1"; PasswordRecord record2 = PasswordHelpers.createPassword1(); record2.encryptedUsername = "different"; record1.guid = "before2"; // Store records. performWait(storeRunnable(session, record1)); performWait(storeRunnable(session, record2)); performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); dispose(session); session = createAndBeginSession(); // Store same records, but with different guids. record1.guid = Utils.generateGuid(); performWait(storeRunnable(session, record1)); performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); record2.guid = Utils.generateGuid(); performWait(storeRunnable(session, record2)); performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); dispose(session); } public void testRawFetch() throws RemoteException { RepositorySession session = createAndBeginSession(); Record[] expected = new Record[] { PasswordHelpers.createPassword1(), PasswordHelpers.createPassword2() }; performWait(storeRunnable(session, expected[0])); performWait(storeRunnable(session, expected[1])); ContentProviderClient client = getApplicationContext().getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI); Cursor cursor = client.query(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null, null, null); assertEquals(2, cursor.getCount()); cursor.moveToFirst(); Set<String> guids = new HashSet<String>(); while (!cursor.isAfterLast()) { String guid = RepoUtils.getStringFromCursor(cursor, BrowserContract.Passwords.GUID); guids.add(guid); cursor.moveToNext(); } cursor.close(); assertEquals(2, guids.size()); assertTrue(guids.contains(expected[0].guid)); assertTrue(guids.contains(expected[1].guid)); dispose(session); } // Helper methods. private RepositorySession createAndBeginSession() { return SessionTestHelper.createAndBeginSession( getApplicationContext(), getRepository()); } private Repository getRepository() { /** * Override this chain in order to avoid our test code having to create two * sessions all the time. Don't track records, so they filtering doesn't happen. */ return new PasswordsRepositorySession.PasswordsRepository() { @Override public void createSession(RepositorySessionCreationDelegate delegate, Context context) { PasswordsRepositorySession session; session = new PasswordsRepositorySession(this, context) { @Override protected synchronized void trackGUID(String guid) { } }; delegate.onSessionCreated(session); } }; } private void wipe() { Context context = getApplicationContext(); context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null); context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null); } private static void dispose(RepositorySession session) { if (session != null) { session.abort(); } } private static long updatePassword(String password, PasswordRecord record, long timestamp) { record.encryptedPassword = password; long modifiedTime = System.currentTimeMillis(); record.timePasswordChanged = record.lastModified = modifiedTime; return modifiedTime; } private static long updatePassword(String password, PasswordRecord record) { return updatePassword(password, record, System.currentTimeMillis()); } // Runnable Helpers. private static Runnable storeRunnable(final RepositorySession session, final Record record) { return new Runnable() { @Override public void run() { session.setStoreDelegate(new ExpectStoredDelegate(record.guid)); try { session.store(record); session.storeDone(); } catch (NoStoreDelegateException e) { fail("NoStoreDelegateException should not occur."); } } }; } private static Runnable fetchAllRunnable(final RepositorySession session, final Record[] records) { return new Runnable() { @Override public void run() { session.fetchAll(new ExpectFetchDelegate(records)); } }; } private static Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { return new Runnable() { @Override public void run() { session.guidsSince(timestamp, new ExpectGuidsSinceDelegate(expected)); } }; } private static Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { return new Runnable() { @Override public void run() { session.fetchSince(timestamp, new ExpectFetchSinceDelegate(timestamp, expected)); } }; } private static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) { return new Runnable() { @Override public void run() { try { session.fetch(guids, new ExpectFetchDelegate(expected)); } catch (InactiveSessionException e) { performNotify(e); } } }; } }