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.sync; michael@0: michael@0: import java.io.IOException; michael@0: michael@0: import org.json.simple.parser.ParseException; michael@0: import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; michael@0: import org.mozilla.gecko.background.testhelpers.BaseMockServerSyncStage; michael@0: import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback; michael@0: import org.mozilla.gecko.background.testhelpers.MockRecord; michael@0: import org.mozilla.gecko.background.testhelpers.MockSharedPreferences; michael@0: import org.mozilla.gecko.background.testhelpers.WBORepository; michael@0: import org.mozilla.gecko.background.testhelpers.WaitHelper; michael@0: import org.mozilla.gecko.sync.EngineSettings; michael@0: import org.mozilla.gecko.sync.GlobalSession; michael@0: import org.mozilla.gecko.sync.MetaGlobalException; michael@0: import org.mozilla.gecko.sync.NonObjectJSONException; michael@0: import org.mozilla.gecko.sync.SyncConfiguration; michael@0: import org.mozilla.gecko.sync.SyncConfigurationException; michael@0: import org.mozilla.gecko.sync.SynchronizerConfiguration; michael@0: import org.mozilla.gecko.sync.crypto.CryptoException; michael@0: import org.mozilla.gecko.sync.crypto.KeyBundle; michael@0: import org.mozilla.gecko.sync.delegates.GlobalSessionCallback; michael@0: import org.mozilla.gecko.sync.net.AuthHeaderProvider; michael@0: import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider; michael@0: import org.mozilla.gecko.sync.repositories.domain.Record; michael@0: import org.mozilla.gecko.sync.stage.NoSuchStageException; michael@0: import org.mozilla.gecko.sync.synchronizer.Synchronizer; michael@0: michael@0: import android.content.SharedPreferences; michael@0: michael@0: /** michael@0: * Test the on-device side effects of reset operations on a stage. michael@0: * michael@0: * See also "TestResetCommands" in the unit test suite. michael@0: */ michael@0: public class TestResetting extends AndroidSyncTestCase { michael@0: private static final String TEST_USERNAME = "johndoe"; michael@0: private static final String TEST_PASSWORD = "password"; michael@0: private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea"; michael@0: michael@0: @Override michael@0: public void setUp() { michael@0: assertTrue(WaitHelper.getTestWaiter().isIdle()); michael@0: } michael@0: michael@0: /** michael@0: * Set up a mock stage that synchronizes two mock repositories. Apply various michael@0: * reset/sync/wipe permutations and check state. michael@0: */ michael@0: public void testResetAndWipeStage() throws Exception { michael@0: michael@0: final long startTime = System.currentTimeMillis(); michael@0: final GlobalSessionCallback callback = createGlobalSessionCallback(); michael@0: final GlobalSession session = createDefaultGlobalSession(callback); michael@0: michael@0: final ExecutableMockServerSyncStage stage = new ExecutableMockServerSyncStage() { michael@0: @Override michael@0: public void onSynchronized(Synchronizer synchronizer) { michael@0: try { michael@0: assertTrue(startTime <= synchronizer.bundleA.getTimestamp()); michael@0: assertTrue(startTime <= synchronizer.bundleB.getTimestamp()); michael@0: michael@0: // Call up to allow the usual persistence etc. to happen. michael@0: super.onSynchronized(synchronizer); michael@0: } catch (Throwable e) { michael@0: performNotify(e); michael@0: return; michael@0: } michael@0: performNotify(); michael@0: } michael@0: }; michael@0: michael@0: final boolean bumpTimestamps = true; michael@0: WBORepository local = new WBORepository(bumpTimestamps); michael@0: WBORepository remote = new WBORepository(bumpTimestamps); michael@0: michael@0: stage.name = "mock"; michael@0: stage.collection = "mock"; michael@0: stage.local = local; michael@0: stage.remote = remote; michael@0: michael@0: stage.executeSynchronously(session); michael@0: michael@0: // Verify the persisted values. michael@0: assertConfigTimestampsGreaterThan(stage.leakConfig(), startTime, startTime); michael@0: michael@0: // Reset. michael@0: stage.resetLocal(session); michael@0: michael@0: // Verify that they're gone. michael@0: assertConfigTimestampsEqual(stage.leakConfig(), 0, 0); michael@0: michael@0: // Now sync data, ensure that timestamps come back. michael@0: final long afterReset = System.currentTimeMillis(); michael@0: final String recordGUID = "abcdefghijkl"; michael@0: local.wbos.put(recordGUID, new MockRecord(recordGUID, "mock", startTime, false)); michael@0: michael@0: // Sync again with data and verify timestamps and data. michael@0: stage.executeSynchronously(session); michael@0: michael@0: assertConfigTimestampsGreaterThan(stage.leakConfig(), afterReset, afterReset); michael@0: assertEquals(1, remote.wbos.size()); michael@0: assertEquals(1, local.wbos.size()); michael@0: michael@0: Record remoteRecord = remote.wbos.get(recordGUID); michael@0: assertNotNull(remoteRecord); michael@0: assertNotNull(local.wbos.get(recordGUID)); michael@0: assertEquals(recordGUID, remoteRecord.guid); michael@0: assertTrue(afterReset <= remoteRecord.lastModified); michael@0: michael@0: // Reset doesn't clear data. michael@0: stage.resetLocal(session); michael@0: assertConfigTimestampsEqual(stage.leakConfig(), 0, 0); michael@0: assertEquals(1, remote.wbos.size()); michael@0: assertEquals(1, local.wbos.size()); michael@0: remoteRecord = remote.wbos.get(recordGUID); michael@0: assertNotNull(remoteRecord); michael@0: assertNotNull(local.wbos.get(recordGUID)); michael@0: michael@0: // Wipe does. Recover from reset... michael@0: final long beforeWipe = System.currentTimeMillis(); michael@0: stage.executeSynchronously(session); michael@0: assertEquals(1, remote.wbos.size()); michael@0: assertEquals(1, local.wbos.size()); michael@0: assertConfigTimestampsGreaterThan(stage.leakConfig(), beforeWipe, beforeWipe); michael@0: michael@0: // ... then wipe. michael@0: stage.wipeLocal(session); michael@0: assertConfigTimestampsEqual(stage.leakConfig(), 0, 0); michael@0: assertEquals(1, remote.wbos.size()); // We don't wipe the server. michael@0: assertEquals(0, local.wbos.size()); // We do wipe local. michael@0: } michael@0: michael@0: /** michael@0: * A stage that joins two Repositories with no wrapping. michael@0: */ michael@0: public class ExecutableMockServerSyncStage extends BaseMockServerSyncStage { michael@0: /** michael@0: * Run this stage synchronously. michael@0: */ michael@0: public void executeSynchronously(final GlobalSession session) { michael@0: final BaseMockServerSyncStage self = this; michael@0: performWait(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: self.execute(session); michael@0: } catch (NoSuchStageException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: private GlobalSession createDefaultGlobalSession(final GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { michael@0: michael@0: final KeyBundle keyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY); michael@0: final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD); michael@0: final SharedPreferences prefs = new MockSharedPreferences(); michael@0: final SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, authHeaderProvider, prefs); michael@0: config.syncKeyBundle = keyBundle; michael@0: return new GlobalSession(config, callback, getApplicationContext(), null, callback) { michael@0: @Override michael@0: public boolean isEngineRemotelyEnabled(String engineName, michael@0: EngineSettings engineSettings) michael@0: throws MetaGlobalException { michael@0: return true; michael@0: } michael@0: michael@0: @Override michael@0: public void advance() { michael@0: // So we don't proceed and run other stages. michael@0: } michael@0: }; michael@0: } michael@0: michael@0: private static GlobalSessionCallback createGlobalSessionCallback() { michael@0: return new DefaultGlobalSessionCallback() { michael@0: michael@0: @Override michael@0: public void handleAborted(GlobalSession globalSession, String reason) { michael@0: performNotify(new Exception("Aborted")); michael@0: } michael@0: michael@0: @Override michael@0: public void handleError(GlobalSession globalSession, Exception ex) { michael@0: performNotify(ex); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: private static void assertConfigTimestampsGreaterThan(SynchronizerConfiguration config, long local, long remote) { michael@0: assertTrue(local <= config.localBundle.getTimestamp()); michael@0: assertTrue(remote <= config.remoteBundle.getTimestamp()); michael@0: } michael@0: michael@0: private static void assertConfigTimestampsEqual(SynchronizerConfiguration config, long local, long remote) { michael@0: assertEquals(local, config.localBundle.getTimestamp()); michael@0: assertEquals(remote, config.remoteBundle.getTimestamp()); michael@0: } michael@0: }