services/sync/tests/unit/test_clients_engine.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     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/record.js");
     8 Cu.import("resource://services-sync/service.js");
     9 Cu.import("resource://services-sync/util.js");
    10 Cu.import("resource://testing-common/services/sync/utils.js");
    12 const MORE_THAN_CLIENTS_TTL_REFRESH = 691200; // 8 days
    13 const LESS_THAN_CLIENTS_TTL_REFRESH = 86400;  // 1 day
    15 let engine = Service.clientsEngine;
    17 /**
    18  * Unpack the record with this ID, and verify that it has the same version that
    19  * we should be putting into records.
    20  */
    21 function check_record_version(user, id) {
    22     let payload = JSON.parse(user.collection("clients").wbo(id).payload);
    24     let rec = new CryptoWrapper();
    25     rec.id = id;
    26     rec.collection = "clients";
    27     rec.ciphertext = payload.ciphertext;
    28     rec.hmac = payload.hmac;
    29     rec.IV = payload.IV;
    31     let cleartext = rec.decrypt(Service.collectionKeys.keyForCollection("clients"));
    33     _("Payload is " + JSON.stringify(cleartext));
    34     do_check_eq(Services.appinfo.version, cleartext.version);
    35     do_check_eq(2, cleartext.protocols.length);
    36     do_check_eq("1.1", cleartext.protocols[0]);
    37     do_check_eq("1.5", cleartext.protocols[1]);
    38 }
    40 add_test(function test_bad_hmac() {
    41   _("Ensure that Clients engine deletes corrupt records.");
    42   let contents = {
    43     meta: {global: {engines: {clients: {version: engine.version,
    44                                         syncID: engine.syncID}}}},
    45     clients: {},
    46     crypto: {}
    47   };
    48   let deletedCollections = [];
    49   let deletedItems       = [];
    50   let callback = {
    51     __proto__: SyncServerCallback,
    52     onItemDeleted: function (username, coll, wboID) {
    53       deletedItems.push(coll + "/" + wboID);
    54     },
    55     onCollectionDeleted: function (username, coll) {
    56       deletedCollections.push(coll);
    57     }
    58   }
    59   let server = serverForUsers({"foo": "password"}, contents, callback);
    60   let user   = server.user("foo");
    62   function check_clients_count(expectedCount) {
    63     let stack = Components.stack.caller;
    64     let coll  = user.collection("clients");
    66     // Treat a non-existent collection as empty.
    67     do_check_eq(expectedCount, coll ? coll.count() : 0, stack);
    68   }
    70   function check_client_deleted(id) {
    71     let coll = user.collection("clients");
    72     let wbo  = coll.wbo(id);
    73     return !wbo || !wbo.payload;
    74   }
    76   function uploadNewKeys() {
    77     generateNewKeys(Service.collectionKeys);
    78     let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
    79     serverKeys.encrypt(Service.identity.syncKeyBundle);
    80     do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success);
    81   }
    83   try {
    84     ensureLegacyIdentityManager();
    85     let passphrase     = "abcdeabcdeabcdeabcdeabcdea";
    86     Service.serverURL  = server.baseURI;
    87     Service.login("foo", "ilovejane", passphrase);
    89     generateNewKeys(Service.collectionKeys);
    91     _("First sync, client record is uploaded");
    92     do_check_eq(engine.lastRecordUpload, 0);
    93     check_clients_count(0);
    94     engine._sync();
    95     check_clients_count(1);
    96     do_check_true(engine.lastRecordUpload > 0);
    98     // Our uploaded record has a version.
    99     check_record_version(user, engine.localID);
   101     // Initial setup can wipe the server, so clean up.
   102     deletedCollections = [];
   103     deletedItems       = [];
   105     _("Change our keys and our client ID, reupload keys.");
   106     let oldLocalID  = engine.localID;     // Preserve to test for deletion!
   107     engine.localID = Utils.makeGUID();
   108     engine.resetClient();
   109     generateNewKeys(Service.collectionKeys);
   110     let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
   111     serverKeys.encrypt(Service.identity.syncKeyBundle);
   112     do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success);
   114     _("Sync.");
   115     engine._sync();
   117     _("Old record " + oldLocalID + " was deleted, new one uploaded.");
   118     check_clients_count(1);
   119     check_client_deleted(oldLocalID);
   121     _("Now change our keys but don't upload them. " +
   122       "That means we get an HMAC error but redownload keys.");
   123     Service.lastHMACEvent = 0;
   124     engine.localID = Utils.makeGUID();
   125     engine.resetClient();
   126     generateNewKeys(Service.collectionKeys);
   127     deletedCollections = [];
   128     deletedItems       = [];
   129     check_clients_count(1);
   130     engine._sync();
   132     _("Old record was not deleted, new one uploaded.");
   133     do_check_eq(deletedCollections.length, 0);
   134     do_check_eq(deletedItems.length, 0);
   135     check_clients_count(2);
   137     _("Now try the scenario where our keys are wrong *and* there's a bad record.");
   138     // Clean up and start fresh.
   139     user.collection("clients")._wbos = {};
   140     Service.lastHMACEvent = 0;
   141     engine.localID = Utils.makeGUID();
   142     engine.resetClient();
   143     deletedCollections = [];
   144     deletedItems       = [];
   145     check_clients_count(0);
   147     uploadNewKeys();
   149     // Sync once to upload a record.
   150     engine._sync();
   151     check_clients_count(1);
   153     // Generate and upload new keys, so the old client record is wrong.
   154     uploadNewKeys();
   156     // Create a new client record and new keys. Now our keys are wrong, as well
   157     // as the object on the server. We'll download the new keys and also delete
   158     // the bad client record.
   159     oldLocalID  = engine.localID;         // Preserve to test for deletion!
   160     engine.localID = Utils.makeGUID();
   161     engine.resetClient();
   162     generateNewKeys(Service.collectionKeys);
   163     let oldKey = Service.collectionKeys.keyForCollection();
   165     do_check_eq(deletedCollections.length, 0);
   166     do_check_eq(deletedItems.length, 0);
   167     engine._sync();
   168     do_check_eq(deletedItems.length, 1);
   169     check_client_deleted(oldLocalID);
   170     check_clients_count(1);
   171     let newKey = Service.collectionKeys.keyForCollection();
   172     do_check_false(oldKey.equals(newKey));
   174   } finally {
   175     Svc.Prefs.resetBranch("");
   176     Service.recordManager.clearCache();
   177     server.stop(run_next_test);
   178   }
   179 });
   181 add_test(function test_properties() {
   182   _("Test lastRecordUpload property");
   183   try {
   184     do_check_eq(Svc.Prefs.get("clients.lastRecordUpload"), undefined);
   185     do_check_eq(engine.lastRecordUpload, 0);
   187     let now = Date.now();
   188     engine.lastRecordUpload = now / 1000;
   189     do_check_eq(engine.lastRecordUpload, Math.floor(now / 1000));
   190   } finally {
   191     Svc.Prefs.resetBranch("");
   192     run_next_test();
   193   }
   194 });
   196 add_test(function test_sync() {
   197   _("Ensure that Clients engine uploads a new client record once a week.");
   199   let contents = {
   200     meta: {global: {engines: {clients: {version: engine.version,
   201                                         syncID: engine.syncID}}}},
   202     clients: {},
   203     crypto: {}
   204   };
   205   let server = serverForUsers({"foo": "password"}, contents);
   206   let user   = server.user("foo");
   208   new SyncTestingInfrastructure(server.server);
   209   generateNewKeys(Service.collectionKeys);
   211   function clientWBO() {
   212     return user.collection("clients").wbo(engine.localID);
   213   }
   215   try {
   217     _("First sync. Client record is uploaded.");
   218     do_check_eq(clientWBO(), undefined);
   219     do_check_eq(engine.lastRecordUpload, 0);
   220     engine._sync();
   221     do_check_true(!!clientWBO().payload);
   222     do_check_true(engine.lastRecordUpload > 0);
   224     _("Let's time travel more than a week back, new record should've been uploaded.");
   225     engine.lastRecordUpload -= MORE_THAN_CLIENTS_TTL_REFRESH;
   226     let lastweek = engine.lastRecordUpload;
   227     clientWBO().payload = undefined;
   228     engine._sync();
   229     do_check_true(!!clientWBO().payload);
   230     do_check_true(engine.lastRecordUpload > lastweek);
   232     _("Remove client record.");
   233     engine.removeClientData();
   234     do_check_eq(clientWBO().payload, undefined);
   236     _("Time travel one day back, no record uploaded.");
   237     engine.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH;
   238     let yesterday = engine.lastRecordUpload;
   239     engine._sync();
   240     do_check_eq(clientWBO().payload, undefined);
   241     do_check_eq(engine.lastRecordUpload, yesterday);
   243   } finally {
   244     Svc.Prefs.resetBranch("");
   245     Service.recordManager.clearCache();
   246     server.stop(run_next_test);
   247   }
   248 });
   250 add_test(function test_client_name_change() {
   251   _("Ensure client name change incurs a client record update.");
   253   let tracker = engine._tracker;
   255   let localID = engine.localID;
   256   let initialName = engine.localName;
   258   Svc.Obs.notify("weave:engine:start-tracking");
   259   _("initial name: " + initialName);
   261   // Tracker already has data, so clear it.
   262   tracker.clearChangedIDs();
   264   let initialScore = tracker.score;
   266   do_check_eq(Object.keys(tracker.changedIDs).length, 0);
   268   Svc.Prefs.set("client.name", "new name");
   270   _("new name: " + engine.localName);
   271   do_check_neq(initialName, engine.localName);
   272   do_check_eq(Object.keys(tracker.changedIDs).length, 1);
   273   do_check_true(engine.localID in tracker.changedIDs);
   274   do_check_true(tracker.score > initialScore);
   275   do_check_true(tracker.score >= SCORE_INCREMENT_XLARGE);
   277   Svc.Obs.notify("weave:engine:stop-tracking");
   279   run_next_test();
   280 });
   282 add_test(function test_send_command() {
   283   _("Verifies _sendCommandToClient puts commands in the outbound queue.");
   285   let store = engine._store;
   286   let tracker = engine._tracker;
   287   let remoteId = Utils.makeGUID();
   288   let rec = new ClientsRec("clients", remoteId);
   290   store.create(rec);
   291   let remoteRecord = store.createRecord(remoteId, "clients");
   293   let action = "testCommand";
   294   let args = ["foo", "bar"];
   296   engine._sendCommandToClient(action, args, remoteId);
   298   let newRecord = store._remoteClients[remoteId];
   299   do_check_neq(newRecord, undefined);
   300   do_check_eq(newRecord.commands.length, 1);
   302   let command = newRecord.commands[0];
   303   do_check_eq(command.command, action);
   304   do_check_eq(command.args.length, 2);
   305   do_check_eq(command.args, args);
   307   do_check_neq(tracker.changedIDs[remoteId], undefined);
   309   run_next_test();
   310 });
   312 add_test(function test_command_validation() {
   313   _("Verifies that command validation works properly.");
   315   let store = engine._store;
   317   let testCommands = [
   318     ["resetAll",    [],       true ],
   319     ["resetAll",    ["foo"],  false],
   320     ["resetEngine", ["tabs"], true ],
   321     ["resetEngine", [],       false],
   322     ["wipeAll",     [],       true ],
   323     ["wipeAll",     ["foo"],  false],
   324     ["wipeEngine",  ["tabs"], true ],
   325     ["wipeEngine",  [],       false],
   326     ["logout",      [],       true ],
   327     ["logout",      ["foo"],  false],
   328     ["__UNKNOWN__", [],       false]
   329   ];
   331   for each (let [action, args, expectedResult] in testCommands) {
   332     let remoteId = Utils.makeGUID();
   333     let rec = new ClientsRec("clients", remoteId);
   335     store.create(rec);
   336     store.createRecord(remoteId, "clients");
   338     engine.sendCommand(action, args, remoteId);
   340     let newRecord = store._remoteClients[remoteId];
   341     do_check_neq(newRecord, undefined);
   343     if (expectedResult) {
   344       _("Ensuring command is sent: " + action);
   345       do_check_eq(newRecord.commands.length, 1);
   347       let command = newRecord.commands[0];
   348       do_check_eq(command.command, action);
   349       do_check_eq(command.args, args);
   351       do_check_neq(engine._tracker, undefined);
   352       do_check_neq(engine._tracker.changedIDs[remoteId], undefined);
   353     } else {
   354       _("Ensuring command is scrubbed: " + action);
   355       do_check_eq(newRecord.commands, undefined);
   357       if (store._tracker) {
   358         do_check_eq(engine._tracker[remoteId], undefined);
   359       }
   360     }
   362   }
   363   run_next_test();
   364 });
   366 add_test(function test_command_duplication() {
   367   _("Ensures duplicate commands are detected and not added");
   369   let store = engine._store;
   370   let remoteId = Utils.makeGUID();
   371   let rec = new ClientsRec("clients", remoteId);
   372   store.create(rec);
   373   store.createRecord(remoteId, "clients");
   375   let action = "resetAll";
   376   let args = [];
   378   engine.sendCommand(action, args, remoteId);
   379   engine.sendCommand(action, args, remoteId);
   381   let newRecord = store._remoteClients[remoteId];
   382   do_check_eq(newRecord.commands.length, 1);
   384   _("Check variant args length");
   385   newRecord.commands = [];
   387   action = "resetEngine";
   388   engine.sendCommand(action, [{ x: "foo" }], remoteId);
   389   engine.sendCommand(action, [{ x: "bar" }], remoteId);
   391   _("Make sure we spot a real dupe argument.");
   392   engine.sendCommand(action, [{ x: "bar" }], remoteId);
   394   do_check_eq(newRecord.commands.length, 2);
   396   run_next_test();
   397 });
   399 add_test(function test_command_invalid_client() {
   400   _("Ensures invalid client IDs are caught");
   402   let id = Utils.makeGUID();
   403   let error;
   405   try {
   406     engine.sendCommand("wipeAll", [], id);
   407   } catch (ex) {
   408     error = ex;
   409   }
   411   do_check_eq(error.message.indexOf("Unknown remote client ID: "), 0);
   413   run_next_test();
   414 });
   416 add_test(function test_process_incoming_commands() {
   417   _("Ensures local commands are executed");
   419   engine.localCommands = [{ command: "logout", args: [] }];
   421   let ev = "weave:service:logout:finish";
   423   var handler = function() {
   424     Svc.Obs.remove(ev, handler);
   425     run_next_test();
   426   };
   428   Svc.Obs.add(ev, handler);
   430   // logout command causes processIncomingCommands to return explicit false.
   431   do_check_false(engine.processIncomingCommands());
   432 });
   434 add_test(function test_command_sync() {
   435   _("Ensure that commands are synced across clients.");
   437   engine._store.wipe();
   438   generateNewKeys(Service.collectionKeys);
   440   let contents = {
   441     meta: {global: {engines: {clients: {version: engine.version,
   442                                         syncID: engine.syncID}}}},
   443     clients: {},
   444     crypto: {}
   445   };
   446   let server   = serverForUsers({"foo": "password"}, contents);
   447   new SyncTestingInfrastructure(server.server);
   449   let user     = server.user("foo");
   450   let remoteId = Utils.makeGUID();
   452   function clientWBO(id) {
   453     return user.collection("clients").wbo(id);
   454   }
   456   _("Create remote client record");
   457   let rec = new ClientsRec("clients", remoteId);
   458   engine._store.create(rec);
   459   let remoteRecord = engine._store.createRecord(remoteId, "clients");
   460   engine.sendCommand("wipeAll", []);
   462   let clientRecord = engine._store._remoteClients[remoteId];
   463   do_check_neq(clientRecord, undefined);
   464   do_check_eq(clientRecord.commands.length, 1);
   466   try {
   467     _("Syncing.");
   468     engine._sync();
   469     _("Checking record was uploaded.");
   470     do_check_neq(clientWBO(engine.localID).payload, undefined);
   471     do_check_true(engine.lastRecordUpload > 0);
   473     do_check_neq(clientWBO(remoteId).payload, undefined);
   475     Svc.Prefs.set("client.GUID", remoteId);
   476     engine._resetClient();
   477     do_check_eq(engine.localID, remoteId);
   478     _("Performing sync on resetted client.");
   479     engine._sync();
   480     do_check_neq(engine.localCommands, undefined);
   481     do_check_eq(engine.localCommands.length, 1);
   483     let command = engine.localCommands[0];
   484     do_check_eq(command.command, "wipeAll");
   485     do_check_eq(command.args.length, 0);
   487   } finally {
   488     Svc.Prefs.resetBranch("");
   489     Service.recordManager.clearCache();
   490     server.stop(run_next_test);
   491   }
   492 });
   494 add_test(function test_send_uri_to_client_for_display() {
   495   _("Ensure sendURIToClientForDisplay() sends command properly.");
   497   let tracker = engine._tracker;
   498   let store = engine._store;
   500   let remoteId = Utils.makeGUID();
   501   let rec = new ClientsRec("clients", remoteId);
   502   rec.name = "remote";
   503   store.create(rec);
   504   let remoteRecord = store.createRecord(remoteId, "clients");
   506   tracker.clearChangedIDs();
   507   let initialScore = tracker.score;
   509   let uri = "http://www.mozilla.org/";
   510   let title = "Title of the Page";
   511   engine.sendURIToClientForDisplay(uri, remoteId, title);
   513   let newRecord = store._remoteClients[remoteId];
   515   do_check_neq(newRecord, undefined);
   516   do_check_eq(newRecord.commands.length, 1);
   518   let command = newRecord.commands[0];
   519   do_check_eq(command.command, "displayURI");
   520   do_check_eq(command.args.length, 3);
   521   do_check_eq(command.args[0], uri);
   522   do_check_eq(command.args[1], engine.localID);
   523   do_check_eq(command.args[2], title);
   525   do_check_true(tracker.score > initialScore);
   526   do_check_true(tracker.score - initialScore >= SCORE_INCREMENT_XLARGE);
   528   _("Ensure unknown client IDs result in exception.");
   529   let unknownId = Utils.makeGUID();
   530   let error;
   532   try {
   533     engine.sendURIToClientForDisplay(uri, unknownId);
   534   } catch (ex) {
   535     error = ex;
   536   }
   538   do_check_eq(error.message.indexOf("Unknown remote client ID: "), 0);
   540   run_next_test();
   541 });
   543 add_test(function test_receive_display_uri() {
   544   _("Ensure processing of received 'displayURI' commands works.");
   546   // We don't set up WBOs and perform syncing because other tests verify
   547   // the command API works as advertised. This saves us a little work.
   549   let uri = "http://www.mozilla.org/";
   550   let remoteId = Utils.makeGUID();
   551   let title = "Page Title!";
   553   let command = {
   554     command: "displayURI",
   555     args: [uri, remoteId, title],
   556   };
   558   engine.localCommands = [command];
   560   // Received 'displayURI' command should result in the topic defined below
   561   // being called.
   562   let ev = "weave:engine:clients:display-uri";
   564   let handler = function(subject, data) {
   565     Svc.Obs.remove(ev, handler);
   567     do_check_eq(subject.uri, uri);
   568     do_check_eq(subject.client, remoteId);
   569     do_check_eq(subject.title, title);
   570     do_check_eq(data, null);
   572     run_next_test();
   573   };
   575   Svc.Obs.add(ev, handler);
   577   do_check_true(engine.processIncomingCommands());
   578 });
   580 function run_test() {
   581   initTestLogging("Trace");
   582   Log.repository.getLogger("Sync.Engine.Clients").level = Log.Level.Trace;
   583   run_next_test();
   584 }

mercurial