/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.sync.middleware.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import junit.framework.AssertionFailedError;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionBeginDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFetchRecordsDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionStoreDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositoryWipeDelegate;
import org.mozilla.android.sync.test.helpers.MockRecord;
import org.mozilla.android.sync.test.helpers.WBORepository;
import org.mozilla.android.sync.test.helpers.WaitHelper;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepositorySession;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
public class TestCrypto5MiddlewareRepositorySession {
public static WaitHelper getTestWaiter() {
return WaitHelper.getTestWaiter();
}
public static void performWait(Runnable runnable) {
getTestWaiter().performWait(runnable);
}
protected static void performNotify(InactiveSessionException e) {
final AssertionFailedError failed = new AssertionFailedError("Inactive session.");
failed.initCause(e);
getTestWaiter().performNotify(failed);
}
protected static void performNotify(InvalidSessionTransitionException e) {
final AssertionFailedError failed = new AssertionFailedError("Invalid session transition.");
failed.initCause(e);
getTestWaiter().performNotify(failed);
}
public Runnable onThreadRunnable(Runnable runnable) {
return WaitHelper.onThreadRunnable(runnable);
}
public WBORepository wboRepo;
public KeyBundle keyBundle;
public Crypto5MiddlewareRepository cmwRepo;
public Crypto5MiddlewareRepositorySession cmwSession;
@Before
public void setUp() throws CryptoException {
wboRepo = new WBORepository();
keyBundle = KeyBundle.withRandomKeys();
cmwRepo = new Crypto5MiddlewareRepository(wboRepo, keyBundle);
cmwSession = null;
}
/**
* Run `runnable` in performWait(... onBeginSucceeded { } ).
*
* The Crypto5MiddlewareRepositorySession is available in self.cmwSession.
*
* @param runnable
*/
public void runInOnBeginSucceeded(final Runnable runnable) {
final TestCrypto5MiddlewareRepositorySession self = this;
performWait(onThreadRunnable(new Runnable() {
@Override
public void run() {
cmwRepo.createSession(new ExpectSuccessRepositorySessionCreationDelegate(getTestWaiter()) {
@Override
public void onSessionCreated(RepositorySession session) {
self.cmwSession = (Crypto5MiddlewareRepositorySession)session;
assertSame(RepositorySession.SessionStatus.UNSTARTED, cmwSession.getStatus());
try {
session.begin(new ExpectSuccessRepositorySessionBeginDelegate(getTestWaiter()) {
@Override
public void onBeginSucceeded(RepositorySession _session) {
assertSame(self.cmwSession, _session);
runnable.run();
}
});
} catch (InvalidSessionTransitionException e) {
TestCrypto5MiddlewareRepositorySession.performNotify(e);
}
}
}, null);
}
}));
}
@Test
/**
* Verify that the status is actually being advanced.
*/
public void testStatus() {
runInOnBeginSucceeded(new Runnable() {
@Override public void run() {
assertSame(RepositorySession.SessionStatus.ACTIVE, cmwSession.getStatus());
try {
cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
});
assertSame(RepositorySession.SessionStatus.DONE, cmwSession.getStatus());
}
@Test
/**
* Verify that wipe is actually wiping the underlying repository.
*/
public void testWipe() {
Record record = new MockRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
wboRepo.wbos.put(record.guid, record);
assertEquals(1, wboRepo.wbos.size());
runInOnBeginSucceeded(new Runnable() {
@Override public void run() {
cmwSession.wipe(new ExpectSuccessRepositoryWipeDelegate(getTestWaiter()));
}
});
performWait(onThreadRunnable(new Runnable() {
@Override public void run() {
try {
cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
}));
assertEquals(0, wboRepo.wbos.size());
}
@Test
/**
* Verify that store is actually writing encrypted data to the underlying repository.
*/
public void testStoreEncrypts() throws NonObjectJSONException, CryptoException, IOException, ParseException {
final BookmarkRecord record = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
record.title = "unencrypted title";
runInOnBeginSucceeded(new Runnable() {
@Override public void run() {
try {
try {
cmwSession.setStoreDelegate(new ExpectSuccessRepositorySessionStoreDelegate(getTestWaiter()));
cmwSession.store(record);
} catch (NoStoreDelegateException e) {
getTestWaiter().performNotify(new AssertionFailedError("Should not happen."));
}
cmwSession.storeDone();
cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
});
assertEquals(1, wboRepo.wbos.size());
assertTrue(wboRepo.wbos.containsKey(record.guid));
Record storedRecord = wboRepo.wbos.get(record.guid);
CryptoRecord cryptoRecord = (CryptoRecord)storedRecord;
assertSame(cryptoRecord.keyBundle, keyBundle);
cryptoRecord = cryptoRecord.decrypt();
BookmarkRecord decryptedRecord = new BookmarkRecord();
decryptedRecord.initFromEnvelope(cryptoRecord);
assertEquals(record.title, decryptedRecord.title);
}
@Test
/**
* Verify that fetch is actually retrieving encrypted data from the underlying repository and is correctly decrypting it.
*/
public void testFetchDecrypts() throws UnsupportedEncodingException, CryptoException {
final BookmarkRecord record1 = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
record1.title = "unencrypted title";
final BookmarkRecord record2 = new BookmarkRecord("XXXXXXXXXXXX", "coll", System.currentTimeMillis(), false);
record2.title = "unencrypted second title";
CryptoRecord encryptedRecord1 = record1.getEnvelope();
encryptedRecord1.keyBundle = keyBundle;
encryptedRecord1 = encryptedRecord1.encrypt();
wboRepo.wbos.put(record1.guid, encryptedRecord1);
CryptoRecord encryptedRecord2 = record2.getEnvelope();
encryptedRecord2.keyBundle = keyBundle;
encryptedRecord2 = encryptedRecord2.encrypt();
wboRepo.wbos.put(record2.guid, encryptedRecord2);
final ExpectSuccessRepositorySessionFetchRecordsDelegate fetchRecordsDelegate = new ExpectSuccessRepositorySessionFetchRecordsDelegate(getTestWaiter());
runInOnBeginSucceeded(new Runnable() {
@Override public void run() {
try {
cmwSession.fetch(new String[] { record1.guid }, fetchRecordsDelegate);
} catch (InactiveSessionException e) {
performNotify(e);
}
}
});
performWait(onThreadRunnable(new Runnable() {
@Override public void run() {
try {
cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
}));
assertEquals(1, fetchRecordsDelegate.fetchedRecords.size());
BookmarkRecord decryptedRecord = new BookmarkRecord();
decryptedRecord.initFromEnvelope((CryptoRecord)fetchRecordsDelegate.fetchedRecords.get(0));
assertEquals(record1.title, decryptedRecord.title);
}
@Test
/**
* Verify that fetchAll is actually retrieving encrypted data from the underlying repository and is correctly decrypting it.
*/
public void testFetchAllDecrypts() throws UnsupportedEncodingException, CryptoException {
final BookmarkRecord record1 = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
record1.title = "unencrypted title";
final BookmarkRecord record2 = new BookmarkRecord("XXXXXXXXXXXX", "coll", System.currentTimeMillis(), false);
record2.title = "unencrypted second title";
CryptoRecord encryptedRecord1 = record1.getEnvelope();
encryptedRecord1.keyBundle = keyBundle;
encryptedRecord1 = encryptedRecord1.encrypt();
wboRepo.wbos.put(record1.guid, encryptedRecord1);
CryptoRecord encryptedRecord2 = record2.getEnvelope();
encryptedRecord2.keyBundle = keyBundle;
encryptedRecord2 = encryptedRecord2.encrypt();
wboRepo.wbos.put(record2.guid, encryptedRecord2);
final ExpectSuccessRepositorySessionFetchRecordsDelegate fetchAllRecordsDelegate = new ExpectSuccessRepositorySessionFetchRecordsDelegate(getTestWaiter());
runInOnBeginSucceeded(new Runnable() {
@Override public void run() {
cmwSession.fetchAll(fetchAllRecordsDelegate);
}
});
performWait(onThreadRunnable(new Runnable() {
@Override public void run() {
try {
cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
} catch (InactiveSessionException e) {
performNotify(e);
}
}
}));
assertEquals(2, fetchAllRecordsDelegate.fetchedRecords.size());
BookmarkRecord decryptedRecord1 = new BookmarkRecord();
decryptedRecord1.initFromEnvelope((CryptoRecord)fetchAllRecordsDelegate.fetchedRecords.get(0));
BookmarkRecord decryptedRecord2 = new BookmarkRecord();
decryptedRecord2.initFromEnvelope((CryptoRecord)fetchAllRecordsDelegate.fetchedRecords.get(1));
// We should get two different decrypted records
assertFalse(decryptedRecord1.guid.equals(decryptedRecord2.guid));
assertFalse(decryptedRecord1.title.equals(decryptedRecord2.title));
// And we should know about both.
assertTrue(record1.title.equals(decryptedRecord1.title) || record1.title.equals(decryptedRecord2.title));
assertTrue(record2.title.equals(decryptedRecord1.title) || record2.title.equals(decryptedRecord2.title));
}
}