@RunWith(TestRunner.class) public class TestGlobalSession { private int TEST_PORT = HTTPServerTestHelper.getTestPort(); private final String TEST_CLUSTER_URL = "http://localhost:" + TEST_PORT; private final String TEST_USERNAME = "******"; private final String TEST_PASSWORD = "******"; private final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea"; private final long TEST_BACKOFF_IN_SECONDS = 2401; public static WaitHelper getTestWaiter() { return WaitHelper.getTestWaiter(); } @Test public void testGetSyncStagesBy() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, NoSuchStageException { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(); GlobalSession s = MockPrefsGlobalSession.getSession( TEST_USERNAME, TEST_PASSWORD, new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, /* context */ null, null); assertTrue( s.getSyncStageByName(Stage.syncBookmarks) instanceof AndroidBrowserBookmarksServerSyncStage); final Set<String> empty = new HashSet<String>(); final Set<String> bookmarksAndTabsNames = new HashSet<String>(); bookmarksAndTabsNames.add("bookmarks"); bookmarksAndTabsNames.add("tabs"); final Set<GlobalSyncStage> bookmarksAndTabsSyncStages = new HashSet<GlobalSyncStage>(); GlobalSyncStage bookmarksStage = s.getSyncStageByName("bookmarks"); GlobalSyncStage tabsStage = s.getSyncStageByName(Stage.syncTabs); bookmarksAndTabsSyncStages.add(bookmarksStage); bookmarksAndTabsSyncStages.add(tabsStage); final Set<Stage> bookmarksAndTabsEnums = new HashSet<Stage>(); bookmarksAndTabsEnums.add(Stage.syncBookmarks); bookmarksAndTabsEnums.add(Stage.syncTabs); assertTrue(s.getSyncStagesByName(empty).isEmpty()); assertEquals( bookmarksAndTabsSyncStages, new HashSet<GlobalSyncStage>(s.getSyncStagesByName(bookmarksAndTabsNames))); assertEquals( bookmarksAndTabsSyncStages, new HashSet<GlobalSyncStage>(s.getSyncStagesByEnum(bookmarksAndTabsEnums))); } /** Test that handleHTTPError does in fact backoff. */ @Test public void testBackoffCalledByHandleHTTPError() { try { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(TEST_CLUSTER_URL); SyncConfiguration config = new SyncConfiguration( TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY)); final GlobalSession session = new MockGlobalSession(config, callback); final HttpResponse response = new BasicHttpResponse( new BasicStatusLine( new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol")); response.addHeader( "X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds. getTestWaiter() .performWait( WaitHelper.onThreadRunnable( new Runnable() { @Override public void run() { session.handleHTTPError( new SyncStorageResponse(response), "Illegal method/protocol"); } })); assertEquals(false, callback.calledSuccess); assertEquals(true, callback.calledError); assertEquals(false, callback.calledAborted); assertEquals(true, callback.calledRequestBackoff); assertEquals( TEST_BACKOFF_IN_SECONDS * 1000, callback.weaveBackoff); // Backoff returned in milliseconds. } catch (Exception e) { e.printStackTrace(); fail("Got exception."); } } /** Test that a trivially successful GlobalSession does not fail or backoff. */ @Test public void testSuccessCalledAfterStages() { try { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(TEST_CLUSTER_URL); SyncConfiguration config = new SyncConfiguration( TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY)); final GlobalSession session = new MockGlobalSession(config, callback); getTestWaiter() .performWait( WaitHelper.onThreadRunnable( new Runnable() { @Override public void run() { try { session.start(); } catch (Exception e) { final AssertionFailedError error = new AssertionFailedError(); error.initCause(e); getTestWaiter().performNotify(error); } } })); assertEquals(true, callback.calledSuccess); assertEquals(false, callback.calledError); assertEquals(false, callback.calledAborted); assertEquals(false, callback.calledRequestBackoff); } catch (Exception e) { e.printStackTrace(); fail("Got exception."); } } /** Test that a failing GlobalSession does in fact fail and back off. */ @Test public void testBackoffCalledInStages() { try { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(TEST_CLUSTER_URL); // Stage fakes a 503 and sets X-Weave-Backoff header to the given seconds. final GlobalSyncStage stage = new MockAbstractNonRepositorySyncStage() { @Override public void execute() { final HttpResponse response = new BasicHttpResponse( new BasicStatusLine( new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol")); response.addHeader( "X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds. session.handleHTTPError( new SyncStorageResponse(response), "Failure fetching info/collections."); } }; // Session installs fake stage to fetch info/collections. SyncConfiguration config = new SyncConfiguration( TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY)); final GlobalSession session = new MockGlobalSession(config, callback).withStage(Stage.fetchInfoCollections, stage); getTestWaiter() .performWait( WaitHelper.onThreadRunnable( new Runnable() { @Override public void run() { try { session.start(); } catch (Exception e) { final AssertionFailedError error = new AssertionFailedError(); error.initCause(e); getTestWaiter().performNotify(error); } } })); assertEquals(false, callback.calledSuccess); assertEquals(true, callback.calledError); assertEquals(false, callback.calledAborted); assertEquals(true, callback.calledRequestBackoff); assertEquals( TEST_BACKOFF_IN_SECONDS * 1000, callback.weaveBackoff); // Backoff returned in milliseconds. } catch (Exception e) { e.printStackTrace(); fail("Got exception."); } } private HTTPServerTestHelper data = new HTTPServerTestHelper(); @SuppressWarnings("static-method") @Before public void setUp() { BaseResource.rewriteLocalhost = false; } public void doRequest() { final WaitHelper innerWaitHelper = new WaitHelper(); innerWaitHelper.performWait( new Runnable() { @Override public void run() { try { final BaseResource r = new BaseResource(TEST_CLUSTER_URL); r.delegate = new MockResourceDelegate(innerWaitHelper); r.get(); } catch (URISyntaxException e) { innerWaitHelper.performNotify(e); } } }); } public MockGlobalSessionCallback doTestSuccess( final boolean stageShouldBackoff, final boolean stageShouldAdvance) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockServer server = new MockServer() { @Override public void handle(Request request, Response response) { if (stageShouldBackoff) { response.set("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); } super.handle(request, response); } }; final MockServerSyncStage stage = new MockServerSyncStage() { @Override public void execute() { // We should have installed our HTTP response observer before starting the sync. assertTrue(BaseResource.isHttpResponseObserver(session)); doRequest(); if (stageShouldAdvance) { session.advance(); return; } session.abort(null, "Stage intentionally failed."); } }; final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(TEST_CLUSTER_URL); SyncConfiguration config = new SyncConfiguration( TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY)); final GlobalSession session = new MockGlobalSession(config, callback).withStage(Stage.syncBookmarks, stage); data.startHTTPServer(server); WaitHelper.getTestWaiter() .performWait( WaitHelper.onThreadRunnable( new Runnable() { @Override public void run() { try { session.start(); } catch (Exception e) { final AssertionFailedError error = new AssertionFailedError(); error.initCause(e); WaitHelper.getTestWaiter().performNotify(error); } } })); data.stopHTTPServer(); // We should have uninstalled our HTTP response observer when the session is terminated. assertFalse(BaseResource.isHttpResponseObserver(session)); return callback; } @Test public void testOnSuccessBackoffAdvanced() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockGlobalSessionCallback callback = doTestSuccess(true, true); assertTrue(callback.calledError); // TODO: this should be calledAborted. assertTrue(callback.calledRequestBackoff); assertEquals(1000 * TEST_BACKOFF_IN_SECONDS, callback.weaveBackoff); } @Test public void testOnSuccessBackoffAborted() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockGlobalSessionCallback callback = doTestSuccess(true, false); assertTrue(callback.calledError); // TODO: this should be calledAborted. assertTrue(callback.calledRequestBackoff); assertEquals(1000 * TEST_BACKOFF_IN_SECONDS, callback.weaveBackoff); } @Test public void testOnSuccessNoBackoffAdvanced() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockGlobalSessionCallback callback = doTestSuccess(false, true); assertTrue(callback.calledSuccess); assertFalse(callback.calledRequestBackoff); } @Test public void testOnSuccessNoBackoffAborted() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockGlobalSessionCallback callback = doTestSuccess(false, false); assertTrue(callback.calledError); // TODO: this should be calledAborted. assertFalse(callback.calledRequestBackoff); } @Test public void testGenerateNewMetaGlobalNonePersisted() throws Exception { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(); final GlobalSession session = MockPrefsGlobalSession.getSession( TEST_USERNAME, TEST_PASSWORD, new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null); // Verify we fill in all of our known engines when none are persisted. session.config.enabledEngineNames = null; MetaGlobal mg = session.generateNewMetaGlobal(); assertEquals(Long.valueOf(GlobalSession.STORAGE_VERSION), mg.getStorageVersion()); assertEquals( VersionConstants.BOOKMARKS_ENGINE_VERSION, mg.getEngines().getObject("bookmarks").getIntegerSafely("version").intValue()); assertEquals( VersionConstants.CLIENTS_ENGINE_VERSION, mg.getEngines().getObject("clients").getIntegerSafely("version").intValue()); List<String> namesList = new ArrayList<String>(mg.getEnabledEngineNames()); Collections.sort(namesList); String[] names = namesList.toArray(new String[namesList.size()]); String[] expected = new String[] {"bookmarks", "clients", "forms", "history", "passwords", "tabs"}; assertArrayEquals(expected, names); } @Test public void testGenerateNewMetaGlobalSomePersisted() throws Exception { final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(); final GlobalSession session = MockPrefsGlobalSession.getSession( TEST_USERNAME, TEST_PASSWORD, new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null); // Verify we preserve engines with version 0 if some are persisted. session.config.enabledEngineNames = new HashSet<String>(); session.config.enabledEngineNames.add("bookmarks"); session.config.enabledEngineNames.add("clients"); session.config.enabledEngineNames.add("addons"); session.config.enabledEngineNames.add("prefs"); MetaGlobal mg = session.generateNewMetaGlobal(); assertEquals(Long.valueOf(GlobalSession.STORAGE_VERSION), mg.getStorageVersion()); assertEquals( VersionConstants.BOOKMARKS_ENGINE_VERSION, mg.getEngines().getObject("bookmarks").getIntegerSafely("version").intValue()); assertEquals( VersionConstants.CLIENTS_ENGINE_VERSION, mg.getEngines().getObject("clients").getIntegerSafely("version").intValue()); assertEquals(0, mg.getEngines().getObject("addons").getIntegerSafely("version").intValue()); assertEquals(0, mg.getEngines().getObject("prefs").getIntegerSafely("version").intValue()); List<String> namesList = new ArrayList<String>(mg.getEnabledEngineNames()); Collections.sort(namesList); String[] names = namesList.toArray(new String[namesList.size()]); String[] expected = new String[] {"addons", "bookmarks", "clients", "prefs"}; assertArrayEquals(expected, names); } @Test public void testUploadUpdatedMetaGlobal() throws Exception { // Set up session with meta/global. final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(); final GlobalSession session = MockPrefsGlobalSession.getSession( TEST_USERNAME, TEST_PASSWORD, new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null); session.config.metaGlobal = session.generateNewMetaGlobal(); session.enginesToUpdate.clear(); // Set enabledEngines in meta/global, including a "new engine." String[] origEngines = new String[] {"bookmarks", "clients", "forms", "history", "tabs", "new-engine"}; ExtendedJSONObject origEnginesJSONObject = new ExtendedJSONObject(); for (String engineName : origEngines) { EngineSettings mockEngineSettings = new EngineSettings(Utils.generateGuid(), Integer.valueOf(0)); origEnginesJSONObject.put(engineName, mockEngineSettings); } session.config.metaGlobal.setEngines(origEnginesJSONObject); // Engines to remove. String[] toRemove = new String[] {"bookmarks", "tabs"}; for (String name : toRemove) { session.removeEngineFromMetaGlobal(name); } // Engines to add. String[] toAdd = new String[] {"passwords"}; for (String name : toAdd) { String syncId = Utils.generateGuid(); session.recordForMetaGlobalUpdate(name, new EngineSettings(syncId, Integer.valueOf(1))); } // Update engines. session.uploadUpdatedMetaGlobal(); // Check resulting enabledEngines. Set<String> expected = new HashSet<String>(); for (String name : origEngines) { expected.add(name); } for (String name : toRemove) { expected.remove(name); } for (String name : toAdd) { expected.add(name); } assertEquals(expected, session.config.metaGlobal.getEnabledEngineNames()); } public void testStageAdvance() { assertEquals(GlobalSession.nextStage(Stage.idle), Stage.checkPreconditions); assertEquals(GlobalSession.nextStage(Stage.completed), Stage.idle); } }
public MockGlobalSessionCallback doTestSuccess( final boolean stageShouldBackoff, final boolean stageShouldAdvance) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { MockServer server = new MockServer() { @Override public void handle(Request request, Response response) { if (stageShouldBackoff) { response.set("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); } super.handle(request, response); } }; final MockServerSyncStage stage = new MockServerSyncStage() { @Override public void execute() { // We should have installed our HTTP response observer before starting the sync. assertTrue(BaseResource.isHttpResponseObserver(session)); doRequest(); if (stageShouldAdvance) { session.advance(); return; } session.abort(null, "Stage intentionally failed."); } }; final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(TEST_CLUSTER_URL); SyncConfiguration config = new SyncConfiguration( TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY)); final GlobalSession session = new MockGlobalSession(config, callback).withStage(Stage.syncBookmarks, stage); data.startHTTPServer(server); WaitHelper.getTestWaiter() .performWait( WaitHelper.onThreadRunnable( new Runnable() { @Override public void run() { try { session.start(); } catch (Exception e) { final AssertionFailedError error = new AssertionFailedError(); error.initCause(e); WaitHelper.getTestWaiter().performNotify(error); } } })); data.stopHTTPServer(); // We should have uninstalled our HTTP response observer when the session is terminated. assertFalse(BaseResource.isHttpResponseObserver(session)); return callback; }