services/sync/tests/unit/test_syncscheduler.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 Cu.import("resource://services-sync/constants.js");
     5 Cu.import("resource://services-sync/engines.js");
     6 Cu.import("resource://services-sync/engines/clients.js");
     7 Cu.import("resource://services-sync/policies.js");
     8 Cu.import("resource://services-sync/record.js");
     9 Cu.import("resource://services-sync/service.js");
    10 Cu.import("resource://services-sync/status.js");
    11 Cu.import("resource://services-sync/util.js");
    12 Cu.import("resource://testing-common/services/sync/utils.js");
    14 Service.engineManager.clear();
    16 function CatapultEngine() {
    17   SyncEngine.call(this, "Catapult", Service);
    18 }
    19 CatapultEngine.prototype = {
    20   __proto__: SyncEngine.prototype,
    21   exception: null, // tests fill this in
    22   _sync: function _sync() {
    23     throw this.exception;
    24   }
    25 };
    27 Service.engineManager.register(CatapultEngine);
    29 let scheduler = new SyncScheduler(Service);
    30 let clientsEngine = Service.clientsEngine;
    32 function sync_httpd_setup() {
    33   let global = new ServerWBO("global", {
    34     syncID: Service.syncID,
    35     storageVersion: STORAGE_VERSION,
    36     engines: {clients: {version: clientsEngine.version,
    37                         syncID: clientsEngine.syncID}}
    38   });
    39   let clientsColl = new ServerCollection({}, true);
    41   // Tracking info/collections.
    42   let collectionsHelper = track_collections_helper();
    43   let upd = collectionsHelper.with_updated_collection;
    45   return httpd_setup({
    46     "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
    47     "/1.1/johndoe/info/collections": collectionsHelper.handler,
    48     "/1.1/johndoe/storage/crypto/keys":
    49       upd("crypto", (new ServerWBO("keys")).handler()),
    50     "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()),
    51     "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "null")
    52   });
    53 }
    55 function setUp(server) {
    56   let deferred = Promise.defer();
    57   configureIdentity({username: "johndoe"}).then(() => {
    58     Service.clusterURL = server.baseURI + "/";
    60     generateNewKeys(Service.collectionKeys);
    61     let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
    62     serverKeys.encrypt(Service.identity.syncKeyBundle);
    63     let result = serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success;
    64     deferred.resolve(result);
    65   });
    66   return deferred.promise;
    67 }
    69 function cleanUpAndGo(server) {
    70   let deferred = Promise.defer();
    71   Utils.nextTick(function () {
    72     Service.startOver();
    73     if (server) {
    74       server.stop(deferred.resolve);
    75     } else {
    76       deferred.resolve();
    77     }
    78   });
    79   return deferred.promise;
    80 }
    82 function run_test() {
    83   initTestLogging("Trace");
    85   Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
    86   Log.repository.getLogger("Sync.scheduler").level = Log.Level.Trace;
    88   // The scheduler checks Weave.fxaEnabled to determine whether to use
    89   // FxA defaults or legacy defaults.  As .fxaEnabled checks the username, we
    90   // set a username here then reset the default to ensure they are used.
    91   ensureLegacyIdentityManager();
    92   setBasicCredentials("johndoe");
    93   scheduler.setDefaults();
    95   run_next_test();
    96 }
    98 add_test(function test_prefAttributes() {
    99   _("Test various attributes corresponding to preferences.");
   101   const INTERVAL = 42 * 60 * 1000;   // 42 minutes
   102   const THRESHOLD = 3142;
   103   const SCORE = 2718;
   104   const TIMESTAMP1 = 1275493471649;
   106   _("The 'nextSync' attribute stores a millisecond timestamp rounded down to the nearest second.");
   107   do_check_eq(scheduler.nextSync, 0);
   108   scheduler.nextSync = TIMESTAMP1;
   109   do_check_eq(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000);
   111   _("'syncInterval' defaults to singleDeviceInterval.");
   112   do_check_eq(Svc.Prefs.get('syncInterval'), undefined);
   113   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   115   _("'syncInterval' corresponds to a preference setting.");
   116   scheduler.syncInterval = INTERVAL;
   117   do_check_eq(scheduler.syncInterval, INTERVAL);
   118   do_check_eq(Svc.Prefs.get('syncInterval'), INTERVAL);
   120   _("'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD");
   121   do_check_eq(Svc.Prefs.get('syncThreshold'), undefined);
   122   do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
   123   scheduler.syncThreshold = THRESHOLD;
   124   do_check_eq(scheduler.syncThreshold, THRESHOLD);
   126   _("'globalScore' corresponds to preference, defaults to zero.");
   127   do_check_eq(Svc.Prefs.get('globalScore'), 0);
   128   do_check_eq(scheduler.globalScore, 0);
   129   scheduler.globalScore = SCORE;
   130   do_check_eq(scheduler.globalScore, SCORE);
   131   do_check_eq(Svc.Prefs.get('globalScore'), SCORE);
   133   _("Intervals correspond to default preferences.");
   134   do_check_eq(scheduler.singleDeviceInterval,
   135               Svc.Prefs.get("scheduler.sync11.singleDeviceInterval") * 1000);
   136   do_check_eq(scheduler.idleInterval,
   137               Svc.Prefs.get("scheduler.idleInterval") * 1000);
   138   do_check_eq(scheduler.activeInterval,
   139               Svc.Prefs.get("scheduler.activeInterval") * 1000);
   140   do_check_eq(scheduler.immediateInterval,
   141               Svc.Prefs.get("scheduler.immediateInterval") * 1000);
   143   _("Custom values for prefs will take effect after a restart.");
   144   Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42);
   145   Svc.Prefs.set("scheduler.idleInterval", 23);
   146   Svc.Prefs.set("scheduler.activeInterval", 18);
   147   Svc.Prefs.set("scheduler.immediateInterval", 31415);
   148   scheduler.setDefaults();
   149   do_check_eq(scheduler.idleInterval, 23000);
   150   do_check_eq(scheduler.singleDeviceInterval, 42000);
   151   do_check_eq(scheduler.activeInterval, 18000);
   152   do_check_eq(scheduler.immediateInterval, 31415000);
   154   Svc.Prefs.resetBranch("");
   155   scheduler.setDefaults();
   156   run_next_test();
   157 });
   159 add_identity_test(this, function test_updateClientMode() {
   160   _("Test updateClientMode adjusts scheduling attributes based on # of clients appropriately");
   161   do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
   162   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   163   do_check_false(scheduler.numClients > 1);
   164   do_check_false(scheduler.idle);
   166   // Trigger a change in interval & threshold by adding a client.
   167   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   168   scheduler.updateClientMode();
   170   do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
   171   do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
   172   do_check_true(scheduler.numClients > 1);
   173   do_check_false(scheduler.idle);
   175   // Resets the number of clients to 0.
   176   clientsEngine.resetClient();
   177   scheduler.updateClientMode();
   179   // Goes back to single user if # clients is 1.
   180   do_check_eq(scheduler.numClients, 1);
   181   do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
   182   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   183   do_check_false(scheduler.numClients > 1);
   184   do_check_false(scheduler.idle);
   186   yield cleanUpAndGo();
   187 });
   189 add_identity_test(this, function test_masterpassword_locked_retry_interval() {
   190   _("Test Status.login = MASTER_PASSWORD_LOCKED results in reschedule at MASTER_PASSWORD interval");
   191   let loginFailed = false;
   192   Svc.Obs.add("weave:service:login:error", function onLoginError() {
   193     Svc.Obs.remove("weave:service:login:error", onLoginError);
   194     loginFailed = true;
   195   });
   197   let rescheduleInterval = false;
   199   let oldScheduleAtInterval = SyncScheduler.prototype.scheduleAtInterval;
   200   SyncScheduler.prototype.scheduleAtInterval = function (interval) {
   201     rescheduleInterval = true;
   202     do_check_eq(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
   203   };
   205   let oldVerifyLogin = Service.verifyLogin;
   206   Service.verifyLogin = function () {
   207     Status.login = MASTER_PASSWORD_LOCKED;
   208     return false;
   209   };
   211   let server = sync_httpd_setup();
   212   yield setUp(server);
   214   Service.sync();
   216   do_check_true(loginFailed);
   217   do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
   218   do_check_true(rescheduleInterval);
   220   Service.verifyLogin = oldVerifyLogin;
   221   SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval;
   223   yield cleanUpAndGo(server);
   224 });
   226 add_identity_test(this, function test_calculateBackoff() {
   227   do_check_eq(Status.backoffInterval, 0);
   229   // Test no interval larger than the maximum backoff is used if
   230   // Status.backoffInterval is smaller.
   231   Status.backoffInterval = 5;
   232   let backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
   233                                                Status.backoffInterval);
   235   do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL);
   237   // Test Status.backoffInterval is used if it is
   238   // larger than MAXIMUM_BACKOFF_INTERVAL.
   239   Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10;
   240   backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
   241                                            Status.backoffInterval);
   243   do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10);
   245   yield cleanUpAndGo();
   246 });
   248 add_identity_test(this, function test_scheduleNextSync_nowOrPast() {
   249   let deferred = Promise.defer();
   250   Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
   251     Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
   252     cleanUpAndGo(server).then(deferred.resolve);
   253   });
   255   let server = sync_httpd_setup();
   256   yield setUp(server);
   258   // We're late for a sync...
   259   scheduler.scheduleNextSync(-1);
   260   yield deferred.promise;
   261 });
   263 add_identity_test(this, function test_scheduleNextSync_future_noBackoff() {
   264   _("scheduleNextSync() uses the current syncInterval if no interval is provided.");
   265   // Test backoffInterval is 0 as expected.
   266   do_check_eq(Status.backoffInterval, 0);
   268   _("Test setting sync interval when nextSync == 0");
   269   scheduler.nextSync = 0;
   270   scheduler.scheduleNextSync();
   272   // nextSync - Date.now() might be smaller than expectedInterval
   273   // since some time has passed since we called scheduleNextSync().
   274   do_check_true(scheduler.nextSync - Date.now()
   275                 <= scheduler.syncInterval);
   276   do_check_eq(scheduler.syncTimer.delay, scheduler.syncInterval);
   278   _("Test setting sync interval when nextSync != 0");
   279   scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
   280   scheduler.scheduleNextSync();
   282   // nextSync - Date.now() might be smaller than expectedInterval
   283   // since some time has passed since we called scheduleNextSync().
   284   do_check_true(scheduler.nextSync - Date.now()
   285                 <= scheduler.syncInterval);
   286   do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
   288   _("Scheduling requests for intervals larger than the current one will be ignored.");
   289   // Request a sync at a longer interval. The sync that's already scheduled
   290   // for sooner takes precedence.
   291   let nextSync = scheduler.nextSync;
   292   let timerDelay = scheduler.syncTimer.delay;
   293   let requestedInterval = scheduler.syncInterval * 10;
   294   scheduler.scheduleNextSync(requestedInterval);
   295   do_check_eq(scheduler.nextSync, nextSync);
   296   do_check_eq(scheduler.syncTimer.delay, timerDelay);
   298   // We can schedule anything we want if there isn't a sync scheduled.
   299   scheduler.nextSync = 0;
   300   scheduler.scheduleNextSync(requestedInterval);
   301   do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
   302   do_check_eq(scheduler.syncTimer.delay, requestedInterval);
   304   // Request a sync at the smallest possible interval (0 triggers now).
   305   scheduler.scheduleNextSync(1);
   306   do_check_true(scheduler.nextSync <= Date.now() + 1);
   307   do_check_eq(scheduler.syncTimer.delay, 1);
   309   yield cleanUpAndGo();
   310 });
   312 add_identity_test(this, function test_scheduleNextSync_future_backoff() {
   313  _("scheduleNextSync() will honour backoff in all scheduling requests.");
   314   // Let's take a backoff interval that's bigger than the default sync interval.
   315   const BACKOFF = 7337;
   316   Status.backoffInterval = scheduler.syncInterval + BACKOFF;
   318   _("Test setting sync interval when nextSync == 0");
   319   scheduler.nextSync = 0;
   320   scheduler.scheduleNextSync();
   322   // nextSync - Date.now() might be smaller than expectedInterval
   323   // since some time has passed since we called scheduleNextSync().
   324   do_check_true(scheduler.nextSync - Date.now()
   325                 <= Status.backoffInterval);
   326   do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
   328   _("Test setting sync interval when nextSync != 0");
   329   scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
   330   scheduler.scheduleNextSync();
   332   // nextSync - Date.now() might be smaller than expectedInterval
   333   // since some time has passed since we called scheduleNextSync().
   334   do_check_true(scheduler.nextSync - Date.now()
   335                 <= Status.backoffInterval);
   336   do_check_true(scheduler.syncTimer.delay <= Status.backoffInterval);
   338   // Request a sync at a longer interval. The sync that's already scheduled
   339   // for sooner takes precedence.
   340   let nextSync = scheduler.nextSync;
   341   let timerDelay = scheduler.syncTimer.delay;
   342   let requestedInterval = scheduler.syncInterval * 10;
   343   do_check_true(requestedInterval > Status.backoffInterval);
   344   scheduler.scheduleNextSync(requestedInterval);
   345   do_check_eq(scheduler.nextSync, nextSync);
   346   do_check_eq(scheduler.syncTimer.delay, timerDelay);
   348   // We can schedule anything we want if there isn't a sync scheduled.
   349   scheduler.nextSync = 0;
   350   scheduler.scheduleNextSync(requestedInterval);
   351   do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
   352   do_check_eq(scheduler.syncTimer.delay, requestedInterval);
   354   // Request a sync at the smallest possible interval (0 triggers now).
   355   scheduler.scheduleNextSync(1);
   356   do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
   357   do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
   359   yield cleanUpAndGo();
   360 });
   362 add_identity_test(this, function test_handleSyncError() {
   363   let server = sync_httpd_setup();
   364   yield setUp(server);
   366   // Force sync to fail.
   367   Svc.Prefs.set("firstSync", "notReady");
   369   _("Ensure expected initial environment.");
   370   do_check_eq(scheduler._syncErrors, 0);
   371   do_check_false(Status.enforceBackoff);
   372   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   373   do_check_eq(Status.backoffInterval, 0);
   375   // Trigger sync with an error several times & observe
   376   // functionality of handleSyncError()
   377   _("Test first error calls scheduleNextSync on default interval");
   378   Service.sync();
   379   do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
   380   do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
   381   do_check_eq(scheduler._syncErrors, 1);
   382   do_check_false(Status.enforceBackoff);
   383   scheduler.syncTimer.clear();
   385   _("Test second error still calls scheduleNextSync on default interval");
   386   Service.sync();
   387   do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
   388   do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
   389   do_check_eq(scheduler._syncErrors, 2);
   390   do_check_false(Status.enforceBackoff);
   391   scheduler.syncTimer.clear();
   393   _("Test third error sets Status.enforceBackoff and calls scheduleAtInterval");
   394   Service.sync();
   395   let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
   396   do_check_eq(Status.backoffInterval, 0);
   397   do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
   398   do_check_true(scheduler.syncTimer.delay <= maxInterval);
   399   do_check_eq(scheduler._syncErrors, 3);
   400   do_check_true(Status.enforceBackoff);
   402   // Status.enforceBackoff is false but there are still errors.
   403   Status.resetBackoff();
   404   do_check_false(Status.enforceBackoff);
   405   do_check_eq(scheduler._syncErrors, 3);
   406   scheduler.syncTimer.clear();
   408   _("Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset");
   409   Service.sync();
   410   maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
   411   do_check_true(scheduler.nextSync <= Date.now() + maxInterval);
   412   do_check_true(scheduler.syncTimer.delay <= maxInterval);
   413   do_check_eq(scheduler._syncErrors, 4);
   414   do_check_true(Status.enforceBackoff);
   415   scheduler.syncTimer.clear();
   417   _("Arrange for a successful sync to reset the scheduler error count");
   418   let deferred = Promise.defer();
   419   Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
   420     Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
   421     cleanUpAndGo(server).then(deferred.resolve);
   422   });
   423   Svc.Prefs.set("firstSync", "wipeRemote");
   424   scheduler.scheduleNextSync(-1);
   425   yield deferred.promise;
   426 });
   428 add_identity_test(this, function test_client_sync_finish_updateClientMode() {
   429   let server = sync_httpd_setup();
   430   yield setUp(server);
   432   // Confirm defaults.
   433   do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
   434   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   435   do_check_false(scheduler.idle);
   437   // Trigger a change in interval & threshold by adding a client.
   438   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   439   do_check_false(scheduler.numClients > 1);
   440   scheduler.updateClientMode();
   441   Service.sync();
   443   do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
   444   do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
   445   do_check_true(scheduler.numClients > 1);
   446   do_check_false(scheduler.idle);
   448   // Resets the number of clients to 0.
   449   clientsEngine.resetClient();
   450   Service.sync();
   452   // Goes back to single user if # clients is 1.
   453   do_check_eq(scheduler.numClients, 1);
   454   do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
   455   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   456   do_check_false(scheduler.numClients > 1);
   457   do_check_false(scheduler.idle);
   459   yield cleanUpAndGo(server);
   460 });
   462 add_identity_test(this, function test_autoconnect_nextSync_past() {
   463   let deferred = Promise.defer();
   464   // nextSync will be 0 by default, so it's way in the past.
   466   Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
   467     Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
   468     cleanUpAndGo(server).then(deferred.resolve);
   469   });
   471   let server = sync_httpd_setup();
   472   yield setUp(server);
   474   scheduler.delayedAutoConnect(0);
   475   yield deferred.promise;
   476 });
   478 add_identity_test(this, function test_autoconnect_nextSync_future() {
   479   let deferred = Promise.defer();
   480   let previousSync = Date.now() + scheduler.syncInterval / 2;
   481   scheduler.nextSync = previousSync;
   482   // nextSync rounds to the nearest second.
   483   let expectedSync = scheduler.nextSync;
   484   let expectedInterval = expectedSync - Date.now() - 1000;
   486   // Ensure we don't actually try to sync (or log in for that matter).
   487   function onLoginStart() {
   488     do_throw("Should not get here!");
   489   }
   490   Svc.Obs.add("weave:service:login:start", onLoginStart);
   492   waitForZeroTimer(function () {
   493     do_check_eq(scheduler.nextSync, expectedSync);
   494     do_check_true(scheduler.syncTimer.delay >= expectedInterval);
   496     Svc.Obs.remove("weave:service:login:start", onLoginStart);
   497     cleanUpAndGo().then(deferred.resolve);
   498   });
   500   yield configureIdentity({username: "johndoe"});
   501   scheduler.delayedAutoConnect(0);
   502   yield deferred.promise;
   503 });
   505 // XXX - this test can't be run with the browserid identity as it relies
   506 // on the syncKey getter behaving in a certain way...
   507 add_task(function test_autoconnect_mp_locked() {
   508   let server = sync_httpd_setup();
   509   yield setUp(server);
   511   // Pretend user did not unlock master password.
   512   let origLocked = Utils.mpLocked;
   513   Utils.mpLocked = function() true;
   515   let origGetter = Service.identity.__lookupGetter__("syncKey");
   516   let origSetter = Service.identity.__lookupSetter__("syncKey");
   517   delete Service.identity.syncKey;
   518   Service.identity.__defineGetter__("syncKey", function() {
   519     _("Faking Master Password entry cancelation.");
   520     throw "User canceled Master Password entry";
   521   });
   523   let deferred = Promise.defer();
   524   // A locked master password will still trigger a sync, but then we'll hit
   525   // MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL.
   526   Svc.Obs.add("weave:service:login:error", function onLoginError() {
   527     Svc.Obs.remove("weave:service:login:error", onLoginError);
   528     Utils.nextTick(function aLittleBitAfterLoginError() {
   529       do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
   531       Utils.mpLocked = origLocked;
   532       delete Service.identity.syncKey;
   533       Service.identity.__defineGetter__("syncKey", origGetter);
   534       Service.identity.__defineSetter__("syncKey", origSetter);
   536       cleanUpAndGo(server).then(deferred.resolve);
   537     });
   538   });
   540   scheduler.delayedAutoConnect(0);
   541   yield deferred.promise;
   542 });
   544 add_identity_test(this, function test_no_autoconnect_during_wizard() {
   545   let server = sync_httpd_setup();
   546   yield setUp(server);
   548   // Simulate the Sync setup wizard.
   549   Svc.Prefs.set("firstSync", "notReady");
   551   // Ensure we don't actually try to sync (or log in for that matter).
   552   function onLoginStart() {
   553     do_throw("Should not get here!");
   554   }
   555   Svc.Obs.add("weave:service:login:start", onLoginStart);
   557   let deferred = Promise.defer();
   558   waitForZeroTimer(function () {
   559     Svc.Obs.remove("weave:service:login:start", onLoginStart);
   560     cleanUpAndGo(server).then(deferred.resolve);
   561   });
   563   scheduler.delayedAutoConnect(0);
   564   yield deferred.promise;
   565 });
   567 add_identity_test(this, function test_no_autoconnect_status_not_ok() {
   568   let server = sync_httpd_setup();
   570   // Ensure we don't actually try to sync (or log in for that matter).
   571   function onLoginStart() {
   572     do_throw("Should not get here!");
   573   }
   574   Svc.Obs.add("weave:service:login:start", onLoginStart);
   576   let deferred = Promise.defer();
   577   waitForZeroTimer(function () {
   578     Svc.Obs.remove("weave:service:login:start", onLoginStart);
   580     do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
   581     do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
   583     cleanUpAndGo(server).then(deferred.resolve);
   584   });
   586   scheduler.delayedAutoConnect(0);
   587   yield deferred.promise;
   588 });
   590 add_identity_test(this, function test_autoconnectDelay_pref() {
   591   let deferred = Promise.defer();
   592   Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
   593     Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
   594     cleanUpAndGo(server).then(deferred.resolve);
   595   });
   597   Svc.Prefs.set("autoconnectDelay", 1);
   599   let server = sync_httpd_setup();
   600   yield setUp(server);
   602   Svc.Obs.notify("weave:service:ready");
   604   // autoconnectDelay pref is multiplied by 1000.
   605   do_check_eq(scheduler._autoTimer.delay, 1000);
   606   do_check_eq(Status.service, STATUS_OK);
   607   yield deferred.promise;
   608 });
   610 add_identity_test(this, function test_idle_adjustSyncInterval() {
   611   // Confirm defaults.
   612   do_check_eq(scheduler.idle, false);
   614   // Single device: nothing changes.
   615   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   616   do_check_eq(scheduler.idle, true);
   617   do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
   619   // Multiple devices: switch to idle interval.
   620   scheduler.idle = false;
   621   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   622   scheduler.updateClientMode();
   623   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   624   do_check_eq(scheduler.idle, true);
   625   do_check_eq(scheduler.syncInterval, scheduler.idleInterval);
   627   yield cleanUpAndGo();
   628 });
   630 add_identity_test(this, function test_back_triggersSync() {
   631   // Confirm defaults.
   632   do_check_false(scheduler.idle);
   633   do_check_eq(Status.backoffInterval, 0);
   635   // Set up: Define 2 clients and put the system in idle.
   636   scheduler.numClients = 2;
   637   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   638   do_check_true(scheduler.idle);
   640   let deferred = Promise.defer();
   641   // We don't actually expect the sync (or the login, for that matter) to
   642   // succeed. We just want to ensure that it was attempted.
   643   Svc.Obs.add("weave:service:login:error", function onLoginError() {
   644     Svc.Obs.remove("weave:service:login:error", onLoginError);
   645     cleanUpAndGo().then(deferred.resolve);
   646   });
   648   // Send an 'active' event to trigger sync soonish.
   649   scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
   650   yield deferred.promise;
   651 });
   653 add_identity_test(this, function test_active_triggersSync_observesBackoff() {
   654   // Confirm defaults.
   655   do_check_false(scheduler.idle);
   657   // Set up: Set backoff, define 2 clients and put the system in idle.
   658   const BACKOFF = 7337;
   659   Status.backoffInterval = scheduler.idleInterval + BACKOFF;
   660   scheduler.numClients = 2;
   661   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   662   do_check_eq(scheduler.idle, true);
   664   function onLoginStart() {
   665     do_throw("Shouldn't have kicked off a sync!");
   666   }
   667   Svc.Obs.add("weave:service:login:start", onLoginStart);
   669   let deferred = Promise.defer();
   670   timer = Utils.namedTimer(function () {
   671     Svc.Obs.remove("weave:service:login:start", onLoginStart);
   673     do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
   674     do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
   676     cleanUpAndGo().then(deferred.resolve);
   677   }, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
   679   // Send an 'active' event to try to trigger sync soonish.
   680   scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
   681   yield deferred.promise;
   682 });
   684 add_identity_test(this, function test_back_debouncing() {
   685   _("Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync.");
   687   // Confirm defaults.
   688   do_check_eq(scheduler.idle, false);
   690   // Set up: Define 2 clients and put the system in idle.
   691   scheduler.numClients = 2;
   692   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   693   do_check_eq(scheduler.idle, true);
   695   function onLoginStart() {
   696     do_throw("Shouldn't have kicked off a sync!");
   697   }
   698   Svc.Obs.add("weave:service:login:start", onLoginStart);
   700   // Create spurious back-then-idle events as observed on OS X:
   701   scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
   702   scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
   704   let deferred = Promise.defer();
   705   timer = Utils.namedTimer(function () {
   706     Svc.Obs.remove("weave:service:login:start", onLoginStart);
   707     cleanUpAndGo().then(deferred.resolve);
   708   }, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
   709   yield deferred.promise;
   710 });
   712 add_identity_test(this, function test_no_sync_node() {
   713   // Test when Status.sync == NO_SYNC_NODE_FOUND
   714   // it is not overwritten on sync:finish
   715   let server = sync_httpd_setup();
   716   yield setUp(server);
   718   Service.serverURL = server.baseURI + "/";
   720   Service.sync();
   721   do_check_eq(Status.sync, NO_SYNC_NODE_FOUND);
   722   do_check_eq(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL);
   724   yield cleanUpAndGo(server);
   725 });
   727 add_identity_test(this, function test_sync_failed_partial_500s() {
   728   _("Test a 5xx status calls handleSyncError.");
   729   scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
   730   let server = sync_httpd_setup();
   732   let engine = Service.engineManager.get("catapult");
   733   engine.enabled = true;
   734   engine.exception = {status: 500};
   736   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   738   do_check_true(yield setUp(server));
   740   Service.sync();
   742   do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
   744   let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
   745   do_check_eq(Status.backoffInterval, 0);
   746   do_check_true(Status.enforceBackoff);
   747   do_check_eq(scheduler._syncErrors, 4);
   748   do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
   749   do_check_true(scheduler.syncTimer.delay <= maxInterval);
   751   yield cleanUpAndGo(server);
   752 });
   754 add_identity_test(this, function test_sync_failed_partial_400s() {
   755   _("Test a non-5xx status doesn't call handleSyncError.");
   756   scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
   757   let server = sync_httpd_setup();
   759   let engine = Service.engineManager.get("catapult");
   760   engine.enabled = true;
   761   engine.exception = {status: 400};
   763   // Have multiple devices for an active interval.
   764   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   766   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   768   do_check_true(yield setUp(server));
   770   Service.sync();
   772   do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
   773   do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
   775   do_check_eq(Status.backoffInterval, 0);
   776   do_check_false(Status.enforceBackoff);
   777   do_check_eq(scheduler._syncErrors, 0);
   778   do_check_true(scheduler.nextSync <= (Date.now() + scheduler.activeInterval));
   779   do_check_true(scheduler.syncTimer.delay <= scheduler.activeInterval);
   781   yield cleanUpAndGo(server);
   782 });
   784 add_identity_test(this, function test_sync_X_Weave_Backoff() {
   785   let server = sync_httpd_setup();
   786   yield setUp(server);
   788   // Use an odd value on purpose so that it doesn't happen to coincide with one
   789   // of the sync intervals.
   790   const BACKOFF = 7337;
   792   // Extend info/collections so that we can put it into server maintenance mode.
   793   const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
   794   let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
   795   let serverBackoff = false;
   796   function infoCollWithBackoff(request, response) {
   797     if (serverBackoff) {
   798       response.setHeader("X-Weave-Backoff", "" + BACKOFF);
   799     }
   800     infoColl(request, response);
   801   }
   802   server.registerPathHandler(INFO_COLLECTIONS, infoCollWithBackoff);
   804   // Pretend we have two clients so that the regular sync interval is
   805   // sufficiently low.
   806   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   807   let rec = clientsEngine._store.createRecord("foo", "clients");
   808   rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
   809   rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
   811   // Sync once to log in and get everything set up. Let's verify our initial
   812   // values.
   813   Service.sync();
   814   do_check_eq(Status.backoffInterval, 0);
   815   do_check_eq(Status.minimumNextSync, 0);
   816   do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
   817   do_check_true(scheduler.nextSync <=
   818                 Date.now() + scheduler.syncInterval);
   819   // Sanity check that we picked the right value for BACKOFF:
   820   do_check_true(scheduler.syncInterval < BACKOFF * 1000);
   822   // Turn on server maintenance and sync again.
   823   serverBackoff = true;
   824   Service.sync();
   826   do_check_true(Status.backoffInterval >= BACKOFF * 1000);
   827   // Allowing 1 second worth of of leeway between when Status.minimumNextSync
   828   // was set and when this line gets executed.
   829   let minimumExpectedDelay = (BACKOFF - 1) * 1000;
   830   do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
   832   // Verify that the next sync is actually going to wait that long.
   833   do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
   834   do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
   836   yield cleanUpAndGo(server);
   837 });
   839 add_identity_test(this, function test_sync_503_Retry_After() {
   840   let server = sync_httpd_setup();
   841   yield setUp(server);
   843   // Use an odd value on purpose so that it doesn't happen to coincide with one
   844   // of the sync intervals.
   845   const BACKOFF = 7337;
   847   // Extend info/collections so that we can put it into server maintenance mode.
   848   const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
   849   let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
   850   let serverMaintenance = false;
   851   function infoCollWithMaintenance(request, response) {
   852     if (!serverMaintenance) {
   853       infoColl(request, response);
   854       return;
   855     }
   856     response.setHeader("Retry-After", "" + BACKOFF);
   857     response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
   858   }
   859   server.registerPathHandler(INFO_COLLECTIONS, infoCollWithMaintenance);
   861   // Pretend we have two clients so that the regular sync interval is
   862   // sufficiently low.
   863   clientsEngine._store.create({id: "foo", cleartext: "bar"});
   864   let rec = clientsEngine._store.createRecord("foo", "clients");
   865   rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
   866   rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
   868   // Sync once to log in and get everything set up. Let's verify our initial
   869   // values.
   870   Service.sync();
   871   do_check_false(Status.enforceBackoff);
   872   do_check_eq(Status.backoffInterval, 0);
   873   do_check_eq(Status.minimumNextSync, 0);
   874   do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
   875   do_check_true(scheduler.nextSync <=
   876                 Date.now() + scheduler.syncInterval);
   877   // Sanity check that we picked the right value for BACKOFF:
   878   do_check_true(scheduler.syncInterval < BACKOFF * 1000);
   880   // Turn on server maintenance and sync again.
   881   serverMaintenance = true;
   882   Service.sync();
   884   do_check_true(Status.enforceBackoff);
   885   do_check_true(Status.backoffInterval >= BACKOFF * 1000);
   886   // Allowing 1 second worth of of leeway between when Status.minimumNextSync
   887   // was set and when this line gets executed.
   888   let minimumExpectedDelay = (BACKOFF - 1) * 1000;
   889   do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
   891   // Verify that the next sync is actually going to wait that long.
   892   do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
   893   do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
   895   yield cleanUpAndGo(server);
   896 });
   898 add_identity_test(this, function test_loginError_recoverable_reschedules() {
   899   _("Verify that a recoverable login error schedules a new sync.");
   900   yield configureIdentity({username: "johndoe"});
   901   Service.serverURL = "http://localhost:1234/";
   902   Service.clusterURL = Service.serverURL;
   903   Service.persistLogin();
   904   Status.resetSync(); // reset Status.login
   906   let deferred = Promise.defer();
   907   Svc.Obs.add("weave:service:login:error", function onLoginError() {
   908     Svc.Obs.remove("weave:service:login:error", onLoginError);
   909     Utils.nextTick(function aLittleBitAfterLoginError() {
   910       do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
   912       let expectedNextSync = Date.now() + scheduler.syncInterval;
   913       do_check_true(scheduler.nextSync > Date.now());
   914       do_check_true(scheduler.nextSync <= expectedNextSync);
   915       do_check_true(scheduler.syncTimer.delay > 0);
   916       do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
   918       Svc.Obs.remove("weave:service:sync:start", onSyncStart);
   919       cleanUpAndGo().then(deferred.resolve);
   920     });
   921   });
   923   // Let's set it up so that a sync is overdue, both in terms of previously
   924   // scheduled syncs and the global score. We still do not expect an immediate
   925   // sync because we just tried (duh).
   926   scheduler.nextSync = Date.now() - 100000;
   927   scheduler.globalScore = SINGLE_USER_THRESHOLD + 1;
   928   function onSyncStart() {
   929     do_throw("Shouldn't have started a sync!");
   930   }
   931   Svc.Obs.add("weave:service:sync:start", onSyncStart);
   933   // Sanity check.
   934   do_check_eq(scheduler.syncTimer, null);
   935   do_check_eq(Status.checkSetup(), STATUS_OK);
   936   do_check_eq(Status.login, LOGIN_SUCCEEDED);
   938   scheduler.scheduleNextSync(0);
   939   yield deferred.promise;
   940 });
   942 add_identity_test(this, function test_loginError_fatal_clearsTriggers() {
   943   _("Verify that a fatal login error clears sync triggers.");
   944   yield configureIdentity({username: "johndoe"});
   946   let server = httpd_setup({
   947     "/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized")
   948   });
   950   Service.serverURL = server.baseURI + "/";
   951   Service.clusterURL = Service.serverURL;
   952   Service.persistLogin();
   953   Status.resetSync(); // reset Status.login
   955   let deferred = Promise.defer();
   956   Svc.Obs.add("weave:service:login:error", function onLoginError() {
   957     Svc.Obs.remove("weave:service:login:error", onLoginError);
   958     Utils.nextTick(function aLittleBitAfterLoginError() {
   959       do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
   961       do_check_eq(scheduler.nextSync, 0);
   962       do_check_eq(scheduler.syncTimer, null);
   964       cleanUpAndGo(server).then(deferred.resolve);
   965     });
   966   });
   968   // Sanity check.
   969   do_check_eq(scheduler.nextSync, 0);
   970   do_check_eq(scheduler.syncTimer, null);
   971   do_check_eq(Status.checkSetup(), STATUS_OK);
   972   do_check_eq(Status.login, LOGIN_SUCCEEDED);
   974   scheduler.scheduleNextSync(0);
   975   yield deferred.promise;
   976 });
   978 add_identity_test(this, function test_proper_interval_on_only_failing() {
   979   _("Ensure proper behavior when only failed records are applied.");
   981   // If an engine reports that no records succeeded, we shouldn't decrease the
   982   // sync interval.
   983   do_check_false(scheduler.hasIncomingItems);
   984   const INTERVAL = 10000000;
   985   scheduler.syncInterval = INTERVAL;
   987   Svc.Obs.notify("weave:service:sync:applied", {
   988     applied: 2,
   989     succeeded: 0,
   990     failed: 2,
   991     newFailed: 2,
   992     reconciled: 0
   993   });
   995   let deferred = Promise.defer();
   996   Utils.nextTick(function() {
   997     scheduler.adjustSyncInterval();
   998     do_check_false(scheduler.hasIncomingItems);
   999     do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
  1001     deferred.resolve();
  1002   });
  1003   yield deferred.promise;
  1004 });

mercurial