/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import java.util.concurrent.ExecutorService;
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.ExpectStoreCompletedDelegate;
import org.mozilla.android.sync.test.helpers.ExpectStoredDelegate;
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.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.repositories.android.FormHistoryRepositorySession;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
public class TestFormHistoryRepositorySession extends AndroidSyncTestCase {
protected ContentProviderClient formsProvider = null;
public TestFormHistoryRepositorySession() throws NoContentProviderException {
super();
}
public void setUp() {
if (formsProvider == null) {
try {
formsProvider = FormHistoryRepositorySession.acquireContentProvider(getApplicationContext());
} catch (NoContentProviderException e) {
fail("Failed to acquireContentProvider: " + e);
}
}
try {
FormHistoryRepositorySession.purgeDatabases(formsProvider);
} catch (RemoteException e) {
fail("Failed to purgeDatabases: " + e);
}
}
public void tearDown() {
if (formsProvider != null) {
formsProvider.release();
formsProvider = null;
}
}
protected FormHistoryRepositorySession.FormHistoryRepository getRepository() {
/**
* Override this chain in order to avoid our test code having to create two
* sessions all the time.
*/
return new FormHistoryRepositorySession.FormHistoryRepository() {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
try {
final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context) {
@Override
protected synchronized void trackGUID(String guid) {
}
};
delegate.onSessionCreated(session);
} catch (Exception e) {
delegate.onSessionCreateFailed(e);
}
}
};
}
protected FormHistoryRepositorySession createSession() {
return (FormHistoryRepositorySession) SessionTestHelper.createSession(
getApplicationContext(),
getRepository());
}
protected FormHistoryRepositorySession createAndBeginSession() {
return (FormHistoryRepositorySession) SessionTestHelper.createAndBeginSession(
getApplicationContext(),
getRepository());
}
public void testAcquire() throws NoContentProviderException {
final FormHistoryRepositorySession session = createAndBeginSession();
assertNotNull(session.getFormsProvider());
session.abort();
}
protected int numRecords(FormHistoryRepositorySession session, Uri uri) throws RemoteException {
Cursor cur = null;
try {
cur = session.getFormsProvider().query(uri, null, null, null, null);
return cur.getCount();
} finally {
if (cur != null) {
cur.close();
}
}
}
protected long after0;
protected long after1;
protected long after2;
protected long after3;
protected long after4;
protected FormHistoryRecord regular1;
protected FormHistoryRecord regular2;
protected FormHistoryRecord deleted1;
protected FormHistoryRecord deleted2;
public void insertTwoRecords(FormHistoryRepositorySession session) throws RemoteException {
Uri regularUri = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
Uri deletedUri = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
after0 = System.currentTimeMillis();
regular1 = new FormHistoryRecord("guid1", "forms", System.currentTimeMillis(), false);
regular1.fieldName = "fieldName1";
regular1.fieldValue = "value1";
final ContentValues cv1 = new ContentValues();
cv1.put(BrowserContract.FormHistory.GUID, regular1.guid);
cv1.put(BrowserContract.FormHistory.FIELD_NAME, regular1.fieldName);
cv1.put(BrowserContract.FormHistory.VALUE, regular1.fieldValue);
cv1.put(BrowserContract.FormHistory.FIRST_USED, 1000 * regular1.lastModified); // Microseconds.
int regularInserted = session.getFormsProvider().bulkInsert(regularUri, new ContentValues[] { cv1 });
assertEquals(1, regularInserted);
after1 = System.currentTimeMillis();
deleted1 = new FormHistoryRecord("guid3", "forms", -1, true);
final ContentValues cv3 = new ContentValues();
cv3.put(BrowserContract.FormHistory.GUID, deleted1.guid);
// cv3.put(BrowserContract.DeletedFormHistory.TIME_DELETED, record3.lastModified); // Set by CP.
int deletedInserted = session.getFormsProvider().bulkInsert(deletedUri, new ContentValues[] { cv3 });
assertEquals(1, deletedInserted);
after2 = System.currentTimeMillis();
regular2 = null;
deleted2 = null;
}
public void insertFourRecords(FormHistoryRepositorySession session) throws RemoteException {
Uri regularUri = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
Uri deletedUri = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
insertTwoRecords(session);
regular2 = new FormHistoryRecord("guid2", "forms", System.currentTimeMillis(), false);
regular2.fieldName = "fieldName2";
regular2.fieldValue = "value2";
final ContentValues cv2 = new ContentValues();
cv2.put(BrowserContract.FormHistory.GUID, regular2.guid);
cv2.put(BrowserContract.FormHistory.FIELD_NAME, regular2.fieldName);
cv2.put(BrowserContract.FormHistory.VALUE, regular2.fieldValue);
cv2.put(BrowserContract.FormHistory.FIRST_USED, 1000 * regular2.lastModified); // Microseconds.
int regularInserted = session.getFormsProvider().bulkInsert(regularUri, new ContentValues[] { cv2 });
assertEquals(1, regularInserted);
after3 = System.currentTimeMillis();
deleted2 = new FormHistoryRecord("guid4", "forms", -1, true);
final ContentValues cv4 = new ContentValues();
cv4.put(BrowserContract.FormHistory.GUID, deleted2.guid);
// cv4.put(BrowserContract.DeletedFormHistory.TIME_DELETED, record4.lastModified); // Set by CP.
int deletedInserted = session.getFormsProvider().bulkInsert(deletedUri, new ContentValues[] { cv4 });
assertEquals(1, deletedInserted);
after4 = System.currentTimeMillis();
}
public void testWipe() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
assertTrue(numRecords(session, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI) > 0);
assertTrue(numRecords(session, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI) > 0);
performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
session.wipe(new RepositorySessionWipeDelegate() {
public void onWipeSucceeded() {
performNotify();
}
public void onWipeFailed(Exception ex) {
performNotify("Wipe should have succeeded", ex);
}
@Override
public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) {
return this;
}
});
}
}));
assertEquals(0, numRecords(session, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI));
assertEquals(0, numRecords(session, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI));
session.abort();
}
protected Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expectedGuids) {
return new Runnable() {
@Override
public void run() {
session.fetchSince(timestamp, new ExpectFetchSinceDelegate(timestamp, expectedGuids));
}
};
}
protected Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) {
return new Runnable() {
@Override
public void run() {
session.fetchAll(new ExpectFetchDelegate(expectedRecords));
}
};
}
protected Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expectedRecords) {
return new Runnable() {
@Override
public void run() {
try {
session.fetch(guids, new ExpectFetchDelegate(expectedRecords));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
};
}
public void testFetchAll() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
performWait(fetchAllRunnable(session, new Record[] { regular1, deleted1 }));
session.abort();
}
public void testFetchByGuid() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
performWait(fetchRunnable(session,
new String[] { regular1.guid, deleted1.guid },
new Record[] { regular1, deleted1 }));
performWait(fetchRunnable(session,
new String[] { regular1.guid },
new Record[] { regular1 }));
performWait(fetchRunnable(session,
new String[] { deleted1.guid, "NON_EXISTENT_GUID?" },
new Record[] { deleted1 }));
performWait(fetchRunnable(session,
new String[] { "FIRST_NON_EXISTENT_GUID", "SECOND_NON_EXISTENT_GUID?" },
new Record[] { }));
session.abort();
}
public void testFetchSince() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertFourRecords(session);
performWait(fetchSinceRunnable(session,
after0, new String[] { regular1.guid, deleted1.guid, regular2.guid, deleted2.guid }));
performWait(fetchSinceRunnable(session,
after1, new String[] { deleted1.guid, regular2.guid, deleted2.guid }));
performWait(fetchSinceRunnable(session,
after2, new String[] { regular2.guid, deleted2.guid }));
performWait(fetchSinceRunnable(session,
after3, new String[] { deleted2.guid }));
performWait(fetchSinceRunnable(session,
after4, new String[] { }));
session.abort();
}
protected Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expectedGuids) {
return new Runnable() {
@Override
public void run() {
session.guidsSince(timestamp, new ExpectGuidsSinceDelegate(expectedGuids));
}
};
}
public void testGuidsSince() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
performWait(guidsSinceRunnable(session,
after0, new String[] { regular1.guid, deleted1.guid }));
performWait(guidsSinceRunnable(session,
after1, new String[] { deleted1.guid}));
performWait(guidsSinceRunnable(session,
after2, new String[] { }));
session.abort();
}
protected Runnable storeRunnable(final RepositorySession session, final Record record, final RepositorySessionStoreDelegate delegate) {
return new Runnable() {
@Override
public void run() {
session.setStoreDelegate(delegate);
try {
session.store(record);
session.storeDone();
} catch (NoStoreDelegateException e) {
performNotify("NoStoreDelegateException should not occur.", e);
}
}
};
}
public void testStoreRemoteNew() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
FormHistoryRecord rec;
// remote regular, local missing => should store.
rec = new FormHistoryRecord("new1", "forms", System.currentTimeMillis(), false);
rec.fieldName = "fieldName1";
rec.fieldValue = "fieldValue1";
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
performWait(fetchRunnable(session, new String[] { rec.guid }, new Record[] { rec }));
// remote deleted, local missing => should delete, but at the moment we ignore.
rec = new FormHistoryRecord("new2", "forms", System.currentTimeMillis(), true);
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
performWait(fetchRunnable(session, new String[] { rec.guid }, new Record[] { }));
session.abort();
}
public void testStoreRemoteNewer() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertFourRecords(session);
long newTimestamp = System.currentTimeMillis();
FormHistoryRecord rec;
// remote regular, local regular, remote newer => should update.
rec = new FormHistoryRecord(regular1.guid, regular1.collection, newTimestamp, false);
rec.fieldName = regular1.fieldName;
rec.fieldValue = regular1.fieldValue + "NEW";
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
performWait(fetchRunnable(session, new String[] { regular1.guid }, new Record[] { rec }));
// remote deleted, local regular, remote newer => should delete everything.
rec = new FormHistoryRecord(regular2.guid, regular2.collection, newTimestamp, true);
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
performWait(fetchRunnable(session, new String[] { regular2.guid }, new Record[] { }));
// remote regular, local deleted, remote newer => should update.
rec = new FormHistoryRecord(deleted1.guid, deleted1.collection, newTimestamp, false);
rec.fieldName = regular1.fieldName;
rec.fieldValue = regular1.fieldValue + "NEW";
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
performWait(fetchRunnable(session, new String[] { deleted1.guid }, new Record[] { rec }));
// remote deleted, local deleted, remote newer => should delete everything.
rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, newTimestamp, true);
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
performWait(fetchRunnable(session, new String[] { deleted2.guid }, new Record[] { }));
session.abort();
}
public static class ExpectNoStoreDelegate extends ExpectStoreCompletedDelegate {
@Override
public void onRecordStoreSucceeded(String guid) {
performNotify("Should not have stored record " + guid, null);
}
}
public void testStoreRemoteOlder() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
long oldTimestamp = System.currentTimeMillis() - 100;
insertFourRecords(session);
FormHistoryRecord rec;
// remote regular, local regular, remote older => should ignore.
rec = new FormHistoryRecord(regular1.guid, regular1.collection, oldTimestamp, false);
rec.fieldName = regular1.fieldName;
rec.fieldValue = regular1.fieldValue + "NEW";
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
// remote deleted, local regular, remote older => should ignore.
rec = new FormHistoryRecord(regular2.guid, regular2.collection, oldTimestamp, true);
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
// remote regular, local deleted, remote older => should ignore.
rec = new FormHistoryRecord(deleted1.guid, deleted1.collection, oldTimestamp, false);
rec.fieldName = regular1.fieldName;
rec.fieldValue = regular1.fieldValue + "NEW";
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
// remote deleted, local deleted, remote older => should ignore.
rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, oldTimestamp, true);
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
session.abort();
}
public void testStoreDifferentGuid() throws NoContentProviderException, RemoteException {
final FormHistoryRepositorySession session = createAndBeginSession();
insertTwoRecords(session);
FormHistoryRecord rec = (FormHistoryRecord) regular1.copyWithIDs("distinct", 999);
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
// Existing record should take remote record's GUID.
performWait(fetchAllRunnable(session, new Record[] { rec, deleted1 }));
session.abort();
}
}