@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 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());
  }
  /**
   * Clean the server, aborting the current sync.
   *
   * <p>
   *
   * <ol>
   *   <li>Wipe the server storage.
   *   <li>Reset all stages and purge cached state: (meta/global and crypto/keys records).
   *   <li>Upload fresh meta/global record.
   *   <li>Upload fresh crypto/keys record.
   *   <li>Restart the sync entirely in order to re-download meta/global and crypto/keys record.
   * </ol>
   *
   * @param session the current session.
   * @param freshStartDelegate delegate to notify on fresh start or failure.
   */
  protected static void freshStart(
      final GlobalSession session, final FreshStartDelegate freshStartDelegate) {
    Logger.debug(LOG_TAG, "Fresh starting.");

    final MetaGlobal mg = session.generateNewMetaGlobal();

    session.wipeServer(
        session.getAuthHeaderProvider(),
        new WipeServerDelegate() {

          @Override
          public void onWiped(long timestamp) {
            Logger.debug(
                LOG_TAG,
                "Successfully wiped server.  Resetting all stages and purging cached meta/global and crypto/keys records.");

            session.resetAllStages();
            session.config.purgeMetaGlobal();
            session.config.purgeCryptoKeys();
            session.config.persistToPrefs();

            Logger.info(LOG_TAG, "Uploading new meta/global with sync ID " + mg.syncID + ".");

            // It would be good to set the X-If-Unmodified-Since header to `timestamp`
            // for this PUT to ensure at least some level of transactionality.
            // Unfortunately, the servers don't support it after a wipe right now
            // (bug 693893), so we're going to defer this until bug 692700.
            mg.upload(
                new MetaGlobalDelegate() {
                  @Override
                  public void handleSuccess(
                      MetaGlobal uploadedGlobal, SyncStorageResponse uploadResponse) {
                    Logger.info(
                        LOG_TAG,
                        "Uploaded new meta/global with sync ID " + uploadedGlobal.syncID + ".");

                    // Generate new keys.
                    CollectionKeys keys = null;
                    try {
                      keys = session.generateNewCryptoKeys();
                    } catch (CryptoException e) {
                      Logger.warn(
                          LOG_TAG, "Got exception generating new keys; failing fresh start.", e);
                      freshStartDelegate.onFreshStartFailed(e);
                    }
                    if (keys == null) {
                      Logger.warn(
                          LOG_TAG, "Got null keys from generateNewKeys; failing fresh start.");
                      freshStartDelegate.onFreshStartFailed(null);
                    }

                    // Upload new keys.
                    Logger.info(LOG_TAG, "Uploading new crypto/keys.");
                    session.uploadKeys(
                        keys,
                        new KeyUploadDelegate() {
                          @Override
                          public void onKeysUploaded() {
                            Logger.info(LOG_TAG, "Uploaded new crypto/keys.");
                            freshStartDelegate.onFreshStart();
                          }

                          @Override
                          public void onKeyUploadFailed(Exception e) {
                            Logger.warn(LOG_TAG, "Got exception uploading new keys.", e);
                            freshStartDelegate.onFreshStartFailed(e);
                          }
                        });
                  }

                  @Override
                  public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
                    // Shouldn't happen on upload.
                    Logger.warn(LOG_TAG, "Got 'missing' response uploading new meta/global.");
                    freshStartDelegate.onFreshStartFailed(
                        new Exception("meta/global missing while uploading."));
                  }

                  @Override
                  public void handleFailure(SyncStorageResponse response) {
                    Logger.warn(
                        LOG_TAG,
                        "Got failure " + response.getStatusCode() + " uploading new meta/global.");
                    session.interpretHTTPFailure(response.httpResponse());
                    freshStartDelegate.onFreshStartFailed(new HTTPFailureException(response));
                  }

                  @Override
                  public void handleError(Exception e) {
                    Logger.warn(LOG_TAG, "Got error uploading new meta/global.", e);
                    freshStartDelegate.onFreshStartFailed(e);
                  }
                });
          }

          @Override
          public void onWipeFailed(Exception e) {
            Logger.warn(LOG_TAG, "Wipe failed.");
            freshStartDelegate.onFreshStartFailed(e);
          }
        });
  }