~ubuntu-branches/ubuntu/oneiric/weave/oneiric

« back to all changes in this revision

Viewing changes to services/sync/tests/unit/test_syncengine_sync.js

  • Committer: Bazaar Package Importer
  • Author(s): Micah Gersten
  • Date: 2010-08-11 00:35:15 UTC
  • mfrom: (3.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20100811003515-o3jbh826bnd1syjv
Tags: 1.4.3-1ubuntu1
* Add -fshort-wchar to CXXFLAGS to fix FTBFS in Ubuntu
  - update debian/rules 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
Cu.import("resource://services-sync/base_records/crypto.js");
 
2
Cu.import("resource://services-sync/base_records/keys.js");
 
3
Cu.import("resource://services-sync/base_records/wbo.js");
 
4
Cu.import("resource://services-sync/constants.js");
 
5
Cu.import("resource://services-sync/engines.js");
 
6
Cu.import("resource://services-sync/identity.js");
 
7
Cu.import("resource://services-sync/resource.js");
 
8
Cu.import("resource://services-sync/stores.js");
 
9
Cu.import("resource://services-sync/trackers.js");
 
10
Cu.import("resource://services-sync/util.js");
 
11
 
 
12
/*
 
13
 * A fake engine implementation.
 
14
 * 
 
15
 * Complete with record, store, and tracker implementations.
 
16
 */
 
17
 
 
18
function SteamRecord(uri) {
 
19
  CryptoWrapper.call(this, uri);
 
20
}
 
21
SteamRecord.prototype = {
 
22
  __proto__: CryptoWrapper.prototype
 
23
};
 
24
Utils.deferGetSet(SteamRecord, "cleartext", ["denomination"]);
 
25
 
 
26
function SteamStore() {
 
27
  Store.call(this, "Steam");
 
28
  this.items = {};
 
29
}
 
30
SteamStore.prototype = {
 
31
  __proto__: Store.prototype,
 
32
 
 
33
  create: function Store_create(record) {
 
34
    this.items[record.id] = record.denomination;
 
35
  },
 
36
 
 
37
  remove: function Store_remove(record) {
 
38
    delete this.items[record.id];
 
39
  },
 
40
 
 
41
  update: function Store_update(record) {
 
42
    this.items[record.id] = record.denomination;
 
43
  },
 
44
 
 
45
  itemExists: function Store_itemExists(id) {
 
46
    return (id in this.items);
 
47
  },
 
48
 
 
49
  createRecord: function(id) {
 
50
    var record = new SteamRecord();
 
51
    record.id = id;
 
52
    record.denomination = this.items[id] || "Data for new record: " + id;
 
53
    return record;
 
54
  },
 
55
 
 
56
  changeItemID: function(oldID, newID) {
 
57
    this.items[newID] = this.items[oldID];
 
58
    delete this.items[oldID];
 
59
  },
 
60
 
 
61
  getAllIDs: function() {
 
62
    let ids = {};
 
63
    for (var id in this.items) {
 
64
      ids[id] = true;
 
65
    }
 
66
    return ids;
 
67
  },
 
68
 
 
69
  wipe: function() {
 
70
    this.items = {};
 
71
  }
 
72
};
 
73
 
 
74
function SteamTracker() {
 
75
  Tracker.call(this, "Steam");
 
76
}
 
77
SteamTracker.prototype = {
 
78
  __proto__: Tracker.prototype
 
79
};
 
80
 
 
81
 
 
82
function SteamEngine() {
 
83
  SyncEngine.call(this, "Steam");
 
84
}
 
85
SteamEngine.prototype = {
 
86
  __proto__: SyncEngine.prototype,
 
87
  _storeObj: SteamStore,
 
88
  _trackerObj: SteamTracker,
 
89
  _recordObj: SteamRecord,
 
90
 
 
91
  _findDupe: function(item) {
 
92
    for (let [id, value] in Iterator(this._store.items)) {
 
93
      if (item.denomination == value) {
 
94
        return id;
 
95
      }
 
96
    }
 
97
  }
 
98
};
 
99
 
 
100
 
 
101
function makeSteamEngine() {
 
102
  return new SteamEngine();
 
103
}
 
104
 
 
105
var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
106
 
 
107
 
 
108
/*
 
109
 * Test setup helpers
 
110
 */
 
111
 
 
112
function sync_httpd_setup(handlers) {
 
113
  handlers["/1.0/foo/storage/meta/global"]
 
114
      = (new ServerWBO('global', {})).handler();
 
115
  handlers["/1.0/foo/storage/keys/pubkey"]
 
116
      = (new ServerWBO('pubkey')).handler();
 
117
  handlers["/1.0/foo/storage/keys/privkey"]
 
118
      = (new ServerWBO('privkey')).handler();
 
119
  return httpd_setup(handlers);
 
120
}
 
121
 
 
122
function createAndUploadKeypair() {
 
123
  let storageURL = Svc.Prefs.get("clusterURL") + Svc.Prefs.get("storageAPI")
 
124
                   + "/" + ID.get("WeaveID").username + "/storage/";
 
125
 
 
126
  PubKeys.defaultKeyUri = storageURL + "keys/pubkey";
 
127
  PrivKeys.defaultKeyUri = storageURL + "keys/privkey";
 
128
  let keys = PubKeys.createKeypair(ID.get("WeaveCryptoID"),
 
129
                                   PubKeys.defaultKeyUri,
 
130
                                   PrivKeys.defaultKeyUri);
 
131
  PubKeys.uploadKeypair(keys);
 
132
}
 
133
 
 
134
function createAndUploadSymKey(url) {
 
135
  let symkey = Svc.Crypto.generateRandomKey();
 
136
  let pubkey = PubKeys.getDefaultKey();
 
137
  let meta = new CryptoMeta(url);
 
138
  meta.addUnwrappedKey(pubkey, symkey);
 
139
  let res = new Resource(meta.uri);
 
140
  res.put(meta);
 
141
}
 
142
 
 
143
// Turn WBO cleartext into "encrypted" payload as it goes over the wire
 
144
function encryptPayload(cleartext) {
 
145
  if (typeof cleartext == "object") {
 
146
    cleartext = JSON.stringify(cleartext);
 
147
  }
 
148
 
 
149
  return {encryption: "http://localhost:8080/1.0/foo/storage/crypto/steam",
 
150
          ciphertext: cleartext, // ciphertext == cleartext with fake crypto
 
151
          IV: "irrelevant",
 
152
          hmac: Utils.sha256HMAC(cleartext, null)};
 
153
}
 
154
 
 
155
 
 
156
/*
 
157
 * Tests
 
158
 * 
 
159
 * SyncEngine._sync() is divided into four rather independent steps:
 
160
 *
 
161
 * - _syncStartup()
 
162
 * - _processIncoming()
 
163
 * - _uploadOutgoing()
 
164
 * - _syncFinish()
 
165
 * 
 
166
 * In the spirit of unit testing, these are tested individually for
 
167
 * different scenarios below.
 
168
 */
 
169
 
 
170
function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
 
171
  _("SyncEngine._syncStartup resets sync and wipes server data if there's no or an oudated global record");
 
172
 
 
173
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
174
  let crypto_steam = new ServerWBO('steam');
 
175
 
 
176
  // Some server side data that's going to be wiped
 
177
  let collection = new ServerCollection();
 
178
  collection.wbos.flying = new ServerWBO(
 
179
      'flying', encryptPayload({id: 'flying',
 
180
                                denomination: "LNER Class A3 4472"}));
 
181
  collection.wbos.scotsman = new ServerWBO(
 
182
      'scotsman', encryptPayload({id: 'scotsman',
 
183
                                  denomination: "Flying Scotsman"}));
 
184
 
 
185
  let server = sync_httpd_setup({
 
186
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
187
      "/1.0/foo/storage/steam": collection.handler()
 
188
  });
 
189
  createAndUploadKeypair();
 
190
 
 
191
  let engine = makeSteamEngine();
 
192
  engine._store.items = {rekolok: "Rekonstruktionslokomotive"};
 
193
  try {
 
194
 
 
195
    // Confirm initial environment
 
196
    do_check_eq(crypto_steam.payload, undefined);
 
197
    do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
 
198
    let metaGlobal = Records.get(engine.metaURL);
 
199
    do_check_eq(metaGlobal.payload.engines, undefined);
 
200
    do_check_true(!!collection.wbos.flying.payload);
 
201
    do_check_true(!!collection.wbos.scotsman.payload);
 
202
 
 
203
    engine.lastSync = Date.now() / 1000;
 
204
    engine._syncStartup();
 
205
 
 
206
    // The meta/global WBO has been filled with data about the engine
 
207
    let engineData = metaGlobal.payload.engines["steam"];
 
208
    do_check_eq(engineData.version, engine.version);
 
209
    do_check_eq(engineData.syncID, engine.syncID);
 
210
 
 
211
    // Sync was reset and server data was wiped
 
212
    do_check_eq(engine.lastSync, 0);
 
213
    do_check_eq(collection.wbos.flying.payload, undefined);
 
214
    do_check_eq(collection.wbos.scotsman.payload, undefined);
 
215
 
 
216
    // Bulk key was uploaded
 
217
    do_check_true(!!crypto_steam.payload);
 
218
    do_check_true(!!crypto_steam.data.keyring);
 
219
 
 
220
    // WBO IDs are added to tracker (they're all marked for uploading)
 
221
    do_check_eq(engine._tracker.changedIDs["rekolok"], 0);
 
222
 
 
223
  } finally {
 
224
    server.stop(function() {});
 
225
    Svc.Prefs.resetBranch("");
 
226
    Records.clearCache();
 
227
    CryptoMetas.clearCache();
 
228
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
229
  }
 
230
}
 
231
 
 
232
function test_syncStartup_metaGet404() {
 
233
  _("SyncEngine._syncStartup resets sync and wipes server data if the symmetric key is missing 404");
 
234
 
 
235
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
236
 
 
237
  // A symmetric key with an incorrect HMAC
 
238
  let crypto_steam = new ServerWBO("steam");
 
239
 
 
240
  // A proper global record with matching version and syncID
 
241
  let engine = makeSteamEngine();
 
242
  let global = new ServerWBO("global",
 
243
                             {engines: {steam: {version: engine.version,
 
244
                                                syncID: engine.syncID}}});
 
245
 
 
246
  // Some server side data that's going to be wiped
 
247
  let collection = new ServerCollection();
 
248
  collection.wbos.flying = new ServerWBO(
 
249
      "flying", encryptPayload({id: "flying",
 
250
                                denomination: "LNER Class A3 4472"}));
 
251
  collection.wbos.scotsman = new ServerWBO(
 
252
      "scotsman", encryptPayload({id: "scotsman",
 
253
                                  denomination: "Flying Scotsman"}));
 
254
 
 
255
  let server = sync_httpd_setup({
 
256
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
257
      "/1.0/foo/storage/steam": collection.handler()
 
258
  });
 
259
  createAndUploadKeypair();
 
260
 
 
261
  try {
 
262
 
 
263
    _("Confirm initial environment");
 
264
    do_check_false(!!crypto_steam.payload);
 
265
    do_check_true(!!collection.wbos.flying.payload);
 
266
    do_check_true(!!collection.wbos.scotsman.payload);
 
267
 
 
268
    engine.lastSync = Date.now() / 1000;
 
269
    engine._syncStartup();
 
270
 
 
271
    _("Sync was reset and server data was wiped");
 
272
    do_check_eq(engine.lastSync, 0);
 
273
    do_check_eq(collection.wbos.flying.payload, undefined);
 
274
    do_check_eq(collection.wbos.scotsman.payload, undefined);
 
275
 
 
276
    _("New bulk key was uploaded");
 
277
    let key = crypto_steam.data.keyring["http://localhost:8080/1.0/foo/storage/keys/pubkey"];
 
278
    do_check_eq(key.wrapped, "fake-symmetric-key-0");
 
279
    do_check_eq(key.hmac, "fake-symmetric-key-0                                            ");
 
280
 
 
281
  } finally {
 
282
    server.stop(function() {});
 
283
    Svc.Prefs.resetBranch("");
 
284
    Records.clearCache();
 
285
    CryptoMetas.clearCache();
 
286
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
287
  }
 
288
}
 
289
 
 
290
function test_syncStartup_failedMetaGet() {
 
291
  _("SyncEngine._syncStartup non-404 failures for getting cryptometa should stop sync");
 
292
 
 
293
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
294
  let server = httpd_setup({
 
295
    "/1.0/foo/storage/crypto/steam": function(request, response) {
 
296
      response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
 
297
      response.bodyOutputStream.write("Fail!", 5);
 
298
    }
 
299
  });
 
300
 
 
301
  let engine = makeSteamEngine();
 
302
  try {
 
303
 
 
304
    _("Getting the cryptometa will fail and should set the appropriate failure");
 
305
    let error;
 
306
    try {
 
307
      engine._syncStartup();
 
308
    } catch (ex) {
 
309
      error = ex;
 
310
    }
 
311
    do_check_eq(error.failureCode, ENGINE_METARECORD_DOWNLOAD_FAIL);
 
312
 
 
313
  } finally {
 
314
    server.stop(function() {});
 
315
    Svc.Prefs.resetBranch("");
 
316
    Records.clearCache();
 
317
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
318
  }
 
319
}
 
320
 
 
321
function test_syncStartup_serverHasNewerVersion() {
 
322
  _("SyncEngine._syncStartup ");
 
323
 
 
324
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
325
  let global = new ServerWBO('global', {engines: {steam: {version: 23456}}});
 
326
  let server = httpd_setup({
 
327
      "/1.0/foo/storage/meta/global": global.handler()
 
328
  });
 
329
 
 
330
  let engine = makeSteamEngine();
 
331
  try {
 
332
 
 
333
    // The server has a newer version of the data and our engine can
 
334
    // handle.  That should give us an exception.
 
335
    let error;
 
336
    try {
 
337
      engine._syncStartup();
 
338
    } catch (ex) {
 
339
      error = ex;
 
340
    }
 
341
    do_check_eq(error.failureCode, VERSION_OUT_OF_DATE);
 
342
 
 
343
  } finally {
 
344
    server.stop(function() {});
 
345
    Svc.Prefs.resetBranch("");
 
346
    Records.clearCache();
 
347
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
348
  }
 
349
}
 
350
 
 
351
 
 
352
function test_syncStartup_syncIDMismatchResetsClient() {
 
353
  _("SyncEngine._syncStartup resets sync if syncIDs don't match");
 
354
 
 
355
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
356
  let crypto_steam = new ServerWBO('steam');
 
357
  let server = sync_httpd_setup({
 
358
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler()
 
359
  });
 
360
 
 
361
  // global record with a different syncID than our engine has
 
362
  let engine = makeSteamEngine();
 
363
  let global = new ServerWBO('global',
 
364
                             {engines: {steam: {version: engine.version,
 
365
                                                syncID: 'foobar'}}});
 
366
  server.registerPathHandler("/1.0/foo/storage/meta/global", global.handler());
 
367
 
 
368
  createAndUploadKeypair();
 
369
 
 
370
  try {
 
371
 
 
372
    // Confirm initial environment
 
373
    do_check_eq(engine.syncID, 'fake-guid-0');
 
374
    do_check_eq(crypto_steam.payload, undefined);
 
375
    do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
 
376
 
 
377
    engine.lastSync = Date.now() / 1000;
 
378
    engine._syncStartup();
 
379
 
 
380
    // The engine has assumed the server's syncID 
 
381
    do_check_eq(engine.syncID, 'foobar');
 
382
 
 
383
    // Sync was reset
 
384
    do_check_eq(engine.lastSync, 0);
 
385
 
 
386
  } finally {
 
387
    server.stop(function() {});
 
388
    Svc.Prefs.resetBranch("");
 
389
    Records.clearCache();
 
390
    CryptoMetas.clearCache();
 
391
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
392
  }
 
393
}
 
394
 
 
395
 
 
396
function test_syncStartup_badKeyWipesServerData() {
 
397
  _("SyncEngine._syncStartup resets sync and wipes server data if there's something wrong with the symmetric key");
 
398
 
 
399
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
400
 
 
401
  // A symmetric key with an incorrect HMAC
 
402
  let crypto_steam = new ServerWBO('steam');
 
403
  crypto_steam.payload = JSON.stringify({
 
404
    keyring: {
 
405
      "http://localhost:8080/1.0/foo/storage/keys/pubkey": {
 
406
        wrapped: Svc.Crypto.generateRandomKey(),
 
407
        hmac: "this-hmac-is-incorrect"
 
408
      }
 
409
    }
 
410
  });
 
411
 
 
412
  // A proper global record with matching version and syncID
 
413
  let engine = makeSteamEngine();
 
414
  let global = new ServerWBO('global',
 
415
                             {engines: {steam: {version: engine.version,
 
416
                                                syncID: engine.syncID}}});
 
417
 
 
418
  // Some server side data that's going to be wiped
 
419
  let collection = new ServerCollection();
 
420
  collection.wbos.flying = new ServerWBO(
 
421
      'flying', encryptPayload({id: 'flying',
 
422
                                denomination: "LNER Class A3 4472"}));
 
423
  collection.wbos.scotsman = new ServerWBO(
 
424
      'scotsman', encryptPayload({id: 'scotsman',
 
425
                                  denomination: "Flying Scotsman"}));
 
426
 
 
427
  let server = sync_httpd_setup({
 
428
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
429
      "/1.0/foo/storage/steam": collection.handler()
 
430
  });
 
431
  createAndUploadKeypair();
 
432
 
 
433
  try {
 
434
 
 
435
    // Confirm initial environment
 
436
    let key = crypto_steam.data.keyring["http://localhost:8080/1.0/foo/storage/keys/pubkey"];
 
437
    do_check_eq(key.wrapped, "fake-symmetric-key-0");
 
438
    do_check_eq(key.hmac, "this-hmac-is-incorrect");
 
439
    do_check_true(!!collection.wbos.flying.payload);
 
440
    do_check_true(!!collection.wbos.scotsman.payload);
 
441
 
 
442
    engine.lastSync = Date.now() / 1000;
 
443
    engine._syncStartup();
 
444
 
 
445
    // Sync was reset and server data was wiped
 
446
    do_check_eq(engine.lastSync, 0);
 
447
    do_check_eq(collection.wbos.flying.payload, undefined);
 
448
    do_check_eq(collection.wbos.scotsman.payload, undefined);
 
449
 
 
450
    // New bulk key was uploaded
 
451
    key = crypto_steam.data.keyring["http://localhost:8080/1.0/foo/storage/keys/pubkey"];
 
452
    do_check_eq(key.wrapped, "fake-symmetric-key-1");
 
453
    do_check_eq(key.hmac, "fake-symmetric-key-1                                            ");
 
454
 
 
455
  } finally {
 
456
    server.stop(function() {});
 
457
    Svc.Prefs.resetBranch("");
 
458
    Records.clearCache();
 
459
    CryptoMetas.clearCache();
 
460
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
461
  }
 
462
}
 
463
 
 
464
 
 
465
function test_processIncoming_emptyServer() {
 
466
  _("SyncEngine._processIncoming working with an empty server backend");
 
467
 
 
468
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
469
  let crypto_steam = new ServerWBO('steam');
 
470
  let collection = new ServerCollection();
 
471
 
 
472
  let server = sync_httpd_setup({
 
473
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
474
      "/1.0/foo/storage/steam": collection.handler()
 
475
  });
 
476
  createAndUploadKeypair();
 
477
 
 
478
  let engine = makeSteamEngine();
 
479
  try {
 
480
 
 
481
    // Merely ensure that this code path is run without any errors
 
482
    engine._processIncoming();
 
483
    do_check_eq(engine.lastSync, 0);
 
484
    do_check_eq(engine.toFetch.length, 0);
 
485
 
 
486
  } finally {
 
487
    server.stop(function() {});
 
488
    Svc.Prefs.resetBranch("");
 
489
    Records.clearCache();
 
490
    CryptoMetas.clearCache();
 
491
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
492
  }
 
493
}
 
494
 
 
495
 
 
496
function test_processIncoming_createFromServer() {
 
497
  _("SyncEngine._processIncoming creates new records from server data");
 
498
 
 
499
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
500
  let crypto_steam = new ServerWBO('steam');
 
501
 
 
502
  // Some server records that will be downloaded
 
503
  let collection = new ServerCollection();
 
504
  collection.wbos.flying = new ServerWBO(
 
505
      'flying', encryptPayload({id: 'flying',
 
506
                                denomination: "LNER Class A3 4472"}));
 
507
  collection.wbos.scotsman = new ServerWBO(
 
508
      'scotsman', encryptPayload({id: 'scotsman',
 
509
                                  denomination: "Flying Scotsman"}));
 
510
 
 
511
  let server = sync_httpd_setup({
 
512
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
513
      "/1.0/foo/storage/steam": collection.handler(),
 
514
      "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
 
515
      "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
 
516
  });
 
517
  createAndUploadKeypair();
 
518
  createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
 
519
 
 
520
  let engine = makeSteamEngine();
 
521
  try {
 
522
 
 
523
    // Confirm initial environment
 
524
    do_check_eq(engine.lastSync, 0);
 
525
    do_check_eq(engine.lastModified, null);
 
526
    do_check_eq(engine._store.items.flying, undefined);
 
527
    do_check_eq(engine._store.items.scotsman, undefined);
 
528
 
 
529
    engine._processIncoming();
 
530
 
 
531
    // Timestamps of last sync and last server modification are set.
 
532
    do_check_true(engine.lastSync > 0);
 
533
    do_check_true(engine.lastModified > 0);
 
534
 
 
535
    // Local records have been created from the server data.
 
536
    do_check_eq(engine._store.items.flying, "LNER Class A3 4472");
 
537
    do_check_eq(engine._store.items.scotsman, "Flying Scotsman");
 
538
 
 
539
  } finally {
 
540
    server.stop(function() {});
 
541
    Svc.Prefs.resetBranch("");
 
542
    Records.clearCache();
 
543
    CryptoMetas.clearCache();
 
544
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
545
  }
 
546
}
 
547
 
 
548
 
 
549
function test_processIncoming_reconcile() {
 
550
  _("SyncEngine._processIncoming updates local records");
 
551
 
 
552
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
553
  let crypto_steam = new ServerWBO('steam');
 
554
  let collection = new ServerCollection();
 
555
 
 
556
  // This server record is newer than the corresponding client one,
 
557
  // so it'll update its data.
 
558
  collection.wbos.newrecord = new ServerWBO(
 
559
      'newrecord', encryptPayload({id: 'newrecord',
 
560
                                   denomination: "New stuff..."}));
 
561
 
 
562
  // This server record is newer than the corresponding client one,
 
563
  // so it'll update its data.
 
564
  collection.wbos.newerserver = new ServerWBO(
 
565
      'newerserver', encryptPayload({id: 'newerserver',
 
566
                                     denomination: "New data!"}));
 
567
 
 
568
  // This server record is 2 mins older than the client counterpart
 
569
  // but identical to it, so we're expecting the client record's
 
570
  // changedID to be reset.
 
571
  collection.wbos.olderidentical = new ServerWBO(
 
572
      'olderidentical', encryptPayload({id: 'olderidentical',
 
573
                                        denomination: "Older but identical"}));
 
574
  collection.wbos.olderidentical.modified -= 120;
 
575
 
 
576
  // This item simply has different data than the corresponding client
 
577
  // record (which is unmodified), so it will update the client as well
 
578
  collection.wbos.updateclient = new ServerWBO(
 
579
      'updateclient', encryptPayload({id: 'updateclient',
 
580
                                      denomination: "Get this!"}));
 
581
 
 
582
  // This is a dupe of 'original' but with a longer GUID, so we're
 
583
  // expecting it to be marked for deletion from the server
 
584
  collection.wbos.duplication = new ServerWBO(
 
585
      'duplication', encryptPayload({id: 'duplication',
 
586
                                     denomination: "Original Entry"}));
 
587
 
 
588
  // This is a dupe of 'long_original' but with a shorter GUID, so we're
 
589
  // expecting it to replace 'long_original'.
 
590
  collection.wbos.dupe = new ServerWBO(
 
591
      'dupe', encryptPayload({id: 'dupe',
 
592
                              denomination: "Long Original Entry"}));  
 
593
 
 
594
  // This record is marked as deleted, so we're expecting the client
 
595
  // record to be removed.
 
596
  collection.wbos.nukeme = new ServerWBO(
 
597
      'nukeme', encryptPayload({id: 'nukeme',
 
598
                                denomination: "Nuke me!",
 
599
                                deleted: true}));
 
600
 
 
601
  let server = sync_httpd_setup({
 
602
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
603
      "/1.0/foo/storage/steam": collection.handler()
 
604
  });
 
605
  createAndUploadKeypair();
 
606
  createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
 
607
 
 
608
  let engine = makeSteamEngine();
 
609
  engine._store.items = {newerserver: "New data, but not as new as server!",
 
610
                         olderidentical: "Older but identical",
 
611
                         updateclient: "Got data?",
 
612
                         original: "Original Entry",
 
613
                         long_original: "Long Original Entry",
 
614
                         nukeme: "Nuke me!"};
 
615
  // Make this record 1 min old, thus older than the one on the server
 
616
  engine._tracker.addChangedID('newerserver', Date.now()/1000 - 60);
 
617
  // This record has been changed 2 mins later than the one on the server
 
618
  engine._tracker.addChangedID('olderidentical', Date.now()/1000);
 
619
 
 
620
  try {
 
621
 
 
622
    // Confirm initial environment
 
623
    do_check_eq(engine._store.items.newrecord, undefined);
 
624
    do_check_eq(engine._store.items.newerserver, "New data, but not as new as server!");
 
625
    do_check_eq(engine._store.items.olderidentical, "Older but identical");
 
626
    do_check_eq(engine._store.items.updateclient, "Got data?");
 
627
    do_check_eq(engine._store.items.nukeme, "Nuke me!");
 
628
    do_check_true(engine._tracker.changedIDs['olderidentical'] > 0);
 
629
 
 
630
    engine._delete = {}; // normally set up by _syncStartup
 
631
    engine._processIncoming();
 
632
 
 
633
    // Timestamps of last sync and last server modification are set.
 
634
    do_check_true(engine.lastSync > 0);
 
635
    do_check_true(engine.lastModified > 0);
 
636
 
 
637
    // The new record is created.
 
638
    do_check_eq(engine._store.items.newrecord, "New stuff...");
 
639
 
 
640
    // The 'newerserver' record is updated since the server data is newer.
 
641
    do_check_eq(engine._store.items.newerserver, "New data!");
 
642
 
 
643
    // The data for 'olderidentical' is identical on the server, so
 
644
    // it's no longer marked as changed anymore.
 
645
    do_check_eq(engine._store.items.olderidentical, "Older but identical");
 
646
    do_check_eq(engine._tracker.changedIDs['olderidentical'], undefined);
 
647
 
 
648
    // Updated with server data.
 
649
    do_check_eq(engine._store.items.updateclient, "Get this!");
 
650
 
 
651
    // The dupe with the shorter ID is kept, the longer one is slated
 
652
    // for deletion.
 
653
    do_check_eq(engine._store.items.long_original, undefined);
 
654
    do_check_eq(engine._store.items.dupe, "Long Original Entry");
 
655
    do_check_neq(engine._delete.ids.indexOf('duplication'), -1);
 
656
 
 
657
    // The 'nukeme' record marked as deleted is removed.
 
658
    do_check_eq(engine._store.items.nukeme, undefined);
 
659
 
 
660
  } finally {
 
661
    server.stop(function() {});
 
662
    Svc.Prefs.resetBranch("");
 
663
    Records.clearCache();
 
664
    CryptoMetas.clearCache();
 
665
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
666
  }
 
667
}
 
668
 
 
669
 
 
670
function test_processIncoming_fetchNum() {
 
671
  _("SyncEngine._processIncoming doesn't fetch everything at ones on mobile clients");
 
672
 
 
673
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
674
  Svc.Prefs.set("client.type", "mobile");
 
675
  let crypto_steam = new ServerWBO('steam');
 
676
  let collection = new ServerCollection();
 
677
 
 
678
  // Let's create some 234 server side records. They're all at least
 
679
  // 10 minutes old.
 
680
  for (var i = 0; i < 234; i++) {
 
681
    let id = 'record-no-' + i;
 
682
    let payload = encryptPayload({id: id, denomination: "Record No. " + i});
 
683
    let wbo = new ServerWBO(id, payload);
 
684
    wbo.modified = Date.now()/1000 - 60*(i+10);
 
685
    collection.wbos[id] = wbo;
 
686
  }
 
687
 
 
688
  let server = sync_httpd_setup({
 
689
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
690
      "/1.0/foo/storage/steam": collection.handler()
 
691
  });
 
692
  createAndUploadKeypair();
 
693
  createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
 
694
 
 
695
  let engine = makeSteamEngine();
 
696
 
 
697
  try {
 
698
 
 
699
    // On a mobile client, the first sync will only get the first 50
 
700
    // objects from the server
 
701
    engine._processIncoming();
 
702
    do_check_eq([id for (id in engine._store.items)].length, 50);
 
703
    do_check_true('record-no-0' in engine._store.items);
 
704
    do_check_true('record-no-49' in engine._store.items);
 
705
    do_check_eq(engine.toFetch.length, 234 - 50);
 
706
 
 
707
 
 
708
    // The next sync will get another 50 objects, assuming the server
 
709
    // hasn't got any new data.
 
710
    engine._processIncoming();
 
711
    do_check_eq([id for (id in engine._store.items)].length, 100);
 
712
    do_check_true('record-no-50' in engine._store.items);
 
713
    do_check_true('record-no-99' in engine._store.items);
 
714
    do_check_eq(engine.toFetch.length, 234 - 100);
 
715
 
 
716
 
 
717
    // Now let's say there are some new items on the server
 
718
    for (i=0; i < 5; i++) {
 
719
      let id = 'new-record-no-' + i;
 
720
      let payload = encryptPayload({id: id, denomination: "New record No. " + i});
 
721
      let wbo = new ServerWBO(id, payload);
 
722
      wbo.modified = Date.now()/1000 - 60*i;
 
723
      collection.wbos[id] = wbo;
 
724
    }
 
725
    // Let's tell the engine the server has got newer data.  This is
 
726
    // normally done by the WeaveSvc after retrieving info/collections.
 
727
    engine.lastModified = Date.now() / 1000 + 1;
 
728
 
 
729
    // Now we'll fetch another 50 items, but 5 of those are the new
 
730
    // ones, so we've only fetched another 45 of the older ones.
 
731
    engine._processIncoming();
 
732
    do_check_eq([id for (id in engine._store.items)].length, 150);
 
733
    do_check_true('new-record-no-0' in engine._store.items);
 
734
    do_check_true('new-record-no-4' in engine._store.items);
 
735
    do_check_true('record-no-100' in engine._store.items);
 
736
    do_check_true('record-no-144' in engine._store.items);
 
737
    do_check_eq(engine.toFetch.length, 234 - 100 - 45);
 
738
 
 
739
 
 
740
    // Now let's modify a few existing records on the server so that
 
741
    // they have to be refetched.
 
742
    collection.wbos['record-no-3'].modified = Date.now()/1000 + 1;
 
743
    collection.wbos['record-no-41'].modified = Date.now()/1000 + 1;
 
744
    collection.wbos['record-no-122'].modified = Date.now()/1000 + 1;
 
745
 
 
746
    // Once again we'll tell the engine that the server's got newer data
 
747
    // and once again we'll fetch 50 items, but 3 of those are the
 
748
    // existing records, so we're only fetching 47 new ones.
 
749
    engine.lastModified = Date.now() / 1000 + 2;
 
750
    engine._processIncoming();
 
751
    do_check_eq([id for (id in engine._store.items)].length, 197);
 
752
    do_check_true('record-no-145' in engine._store.items);
 
753
    do_check_true('record-no-191' in engine._store.items);
 
754
    do_check_eq(engine.toFetch.length, 234 - 100 - 45 - 47);
 
755
 
 
756
 
 
757
    // Finally let's fetch the rest, making sure that will fetch
 
758
    // everything up to the last record.
 
759
    while(engine.toFetch.length) {
 
760
      engine._processIncoming();
 
761
    }
 
762
    do_check_eq([id for (id in engine._store.items)].length, 234 + 5);
 
763
    do_check_true('record-no-233' in engine._store.items);
 
764
 
 
765
  } finally {
 
766
    server.stop(function() {});
 
767
    Svc.Prefs.resetBranch("");
 
768
    Records.clearCache();
 
769
    CryptoMetas.clearCache();
 
770
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
771
  }
 
772
}
 
773
 
 
774
 
 
775
function test_uploadOutgoing_toEmptyServer() {
 
776
  _("SyncEngine._uploadOutgoing uploads new records to server");
 
777
 
 
778
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
779
  let crypto_steam = new ServerWBO('steam');
 
780
  let collection = new ServerCollection();
 
781
  collection.wbos.flying = new ServerWBO('flying');
 
782
  collection.wbos.scotsman = new ServerWBO('scotsman');
 
783
 
 
784
  let server = sync_httpd_setup({
 
785
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
786
      "/1.0/foo/storage/steam": collection.handler(),
 
787
      "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
 
788
      "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
 
789
  });
 
790
  createAndUploadKeypair();
 
791
  createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
 
792
 
 
793
  let engine = makeSteamEngine();
 
794
  engine._store.items = {flying: "LNER Class A3 4472",
 
795
                         scotsman: "Flying Scotsman"};
 
796
  // Mark one of these records as changed 
 
797
  engine._tracker.addChangedID('scotsman', 0);
 
798
 
 
799
  try {
 
800
 
 
801
    // Confirm initial environment
 
802
    do_check_eq(collection.wbos.flying.payload, undefined);
 
803
    do_check_eq(collection.wbos.scotsman.payload, undefined);
 
804
    do_check_eq(engine._tracker.changedIDs['scotsman'], 0);
 
805
 
 
806
    engine._uploadOutgoing();
 
807
 
 
808
    // Ensure the marked record ('scotsman') has been uploaded and is
 
809
    // no longer marked.
 
810
    do_check_eq(collection.wbos.flying.payload, undefined);
 
811
    do_check_true(!!collection.wbos.scotsman.payload);
 
812
    do_check_eq(JSON.parse(collection.wbos.scotsman.data.ciphertext).id,
 
813
                'scotsman');
 
814
    do_check_eq(engine._tracker.changedIDs['scotsman'], undefined);
 
815
 
 
816
    // The 'flying' record wasn't marked so it wasn't uploaded
 
817
    do_check_eq(collection.wbos.flying.payload, undefined);
 
818
 
 
819
  } finally {
 
820
    server.stop(function() {});
 
821
    Svc.Prefs.resetBranch("");
 
822
    Records.clearCache();
 
823
    CryptoMetas.clearCache();
 
824
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
825
  }
 
826
}
 
827
 
 
828
 
 
829
function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
 
830
  _("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_RECORDS");
 
831
 
 
832
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
833
  let crypto_steam = new ServerWBO('steam');
 
834
  let collection = new ServerCollection();
 
835
 
 
836
  // Let's count how many times the client posts to the server
 
837
  var noOfUploads = 0;
 
838
  collection.post = (function(orig) {
 
839
    return function() {
 
840
      noOfUploads++;
 
841
      return orig.apply(this, arguments);
 
842
    };
 
843
  }(collection.post));
 
844
 
 
845
  // Create a bunch of records (and server side handlers)
 
846
  let engine = makeSteamEngine();
 
847
  for (var i = 0; i < 234; i++) {
 
848
    let id = 'record-no-' + i;
 
849
    engine._store.items[id] = "Record No. " + i;
 
850
    engine._tracker.addChangedID(id, 0);
 
851
    collection.wbos[id] = new ServerWBO(id);
 
852
  }
 
853
 
 
854
  let server = sync_httpd_setup({
 
855
      "/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
 
856
      "/1.0/foo/storage/steam": collection.handler()
 
857
  });
 
858
  createAndUploadKeypair();
 
859
  createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
 
860
 
 
861
  try {
 
862
 
 
863
    // Confirm initial environment
 
864
    do_check_eq(noOfUploads, 0);
 
865
 
 
866
    engine._uploadOutgoing();
 
867
 
 
868
    // Ensure all records have been uploaded
 
869
    for (i = 0; i < 234; i++) {
 
870
      do_check_true(!!collection.wbos['record-no-'+i].payload);
 
871
    }
 
872
 
 
873
    // Ensure that the uploads were performed in batches of MAX_UPLOAD_RECORDS
 
874
    do_check_eq(noOfUploads, Math.ceil(234/MAX_UPLOAD_RECORDS));
 
875
 
 
876
  } finally {
 
877
    server.stop(function() {});
 
878
    Svc.Prefs.resetBranch("");
 
879
    Records.clearCache();
 
880
    CryptoMetas.clearCache();
 
881
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
882
  }
 
883
}
 
884
 
 
885
 
 
886
function test_syncFinish_noDelete() {
 
887
  _("SyncEngine._syncFinish resets tracker's score");
 
888
  let engine = makeSteamEngine();
 
889
  engine._delete = {}; // Nothing to delete
 
890
  engine._tracker.score = 100;
 
891
 
 
892
  // _syncFinish() will reset the engine's score.
 
893
  engine._syncFinish();
 
894
  do_check_eq(engine.score, 0);
 
895
}
 
896
 
 
897
 
 
898
function test_syncFinish_deleteByIds() {
 
899
  _("SyncEngine._syncFinish deletes server records slated for deletion (list of record IDs).");
 
900
 
 
901
  Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
902
  let collection = new ServerCollection();
 
903
  collection.wbos.flying = new ServerWBO(
 
904
      'flying', encryptPayload({id: 'flying',
 
905
                                denomination: "LNER Class A3 4472"}));
 
906
  collection.wbos.scotsman = new ServerWBO(
 
907
      'scotsman', encryptPayload({id: 'scotsman',
 
908
                                  denomination: "Flying Scotsman"}));
 
909
  collection.wbos.rekolok = new ServerWBO(
 
910
      'rekolok', encryptPayload({id: 'rekolok',
 
911
                                denomination: "Rekonstruktionslokomotive"}));
 
912
 
 
913
  let server = httpd_setup({
 
914
      "/1.0/foo/storage/steam": collection.handler()
 
915
  });
 
916
 
 
917
  let engine = makeSteamEngine();
 
918
  try {
 
919
    engine._delete = {ids: ['flying', 'rekolok']};
 
920
    engine._syncFinish();
 
921
 
 
922
    // The 'flying' and 'rekolok' records were deleted while the
 
923
    // 'scotsman' one wasn't.
 
924
    do_check_eq(collection.wbos.flying.payload, undefined);
 
925
    do_check_true(!!collection.wbos.scotsman.payload);
 
926
    do_check_eq(collection.wbos.rekolok.payload, undefined);
 
927
 
 
928
    // The deletion todo list has been reset.
 
929
    do_check_eq(engine._delete.ids, undefined);
 
930
 
 
931
  } finally {
 
932
    server.stop(function() {});
 
933
    Svc.Prefs.resetBranch("");
 
934
    Records.clearCache();
 
935
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
936
  }
 
937
}
 
938
 
 
939
 
 
940
function test_syncFinish_deleteLotsInBatches() {
 
941
  _("SyncEngine._syncFinish deletes server records in batches of 100 (list of record IDs).");
 
942
 
 
943
 Svc.Prefs.set("clusterURL", "http://localhost:8080/");
 
944
  let collection = new ServerCollection();
 
945
 
 
946
  // Let's count how many times the client does a DELETE request to the server
 
947
  var noOfUploads = 0;
 
948
  collection.delete = (function(orig) {
 
949
    return function() {
 
950
      noOfUploads++;
 
951
      return orig.apply(this, arguments);
 
952
    };
 
953
  }(collection.delete));
 
954
 
 
955
  // Create a bunch of records on the server
 
956
  let now = Date.now();
 
957
  for (var i = 0; i < 234; i++) {
 
958
    let id = 'record-no-' + i;
 
959
    let payload = encryptPayload({id: id, denomination: "Record No. " + i});
 
960
    let wbo = new ServerWBO(id, payload);
 
961
    wbo.modified = now / 1000 - 60 * (i + 110);
 
962
    collection.wbos[id] = wbo;
 
963
  }
 
964
 
 
965
  let server = httpd_setup({
 
966
      "/1.0/foo/storage/steam": collection.handler()
 
967
  });
 
968
 
 
969
  let engine = makeSteamEngine();
 
970
  try {
 
971
 
 
972
    // Confirm initial environment
 
973
    do_check_eq(noOfUploads, 0);
 
974
 
 
975
    // Declare what we want to have deleted: all records no. 100 and
 
976
    // up and all records that are less than 200 mins old (which are
 
977
    // records 0 thru 90).
 
978
    engine._delete = {ids: [],
 
979
                      newer: now / 1000 - 60 * 200.5};
 
980
    for (i = 100; i < 234; i++) {
 
981
      engine._delete.ids.push('record-no-' + i);
 
982
    }
 
983
 
 
984
    engine._syncFinish();
 
985
 
 
986
    // Ensure that the appropriate server data has been wiped while
 
987
    // preserving records 90 thru 200.
 
988
    for (i = 0; i < 234; i++) {
 
989
      let id = 'record-no-' + i;
 
990
      if (i <= 90 || i >= 100) {
 
991
        do_check_eq(collection.wbos[id].payload, undefined);
 
992
      } else {
 
993
        do_check_true(!!collection.wbos[id].payload);
 
994
      }
 
995
    }
 
996
 
 
997
    // The deletion was done in batches
 
998
    do_check_eq(noOfUploads, 2 + 1);
 
999
 
 
1000
    // The deletion todo list has been reset.
 
1001
    do_check_eq(engine._delete.ids, undefined);
 
1002
 
 
1003
  } finally {
 
1004
    server.stop(function() {});
 
1005
    Svc.Prefs.resetBranch("");
 
1006
    Records.clearCache();
 
1007
    syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
1008
  }
 
1009
}
 
1010
 
 
1011
function run_test() {
 
1012
  test_syncStartup_emptyOrOutdatedGlobalsResetsSync();
 
1013
  test_syncStartup_metaGet404();
 
1014
  test_syncStartup_failedMetaGet();
 
1015
  test_syncStartup_serverHasNewerVersion();
 
1016
  test_syncStartup_syncIDMismatchResetsClient();
 
1017
  test_syncStartup_badKeyWipesServerData();
 
1018
  test_processIncoming_emptyServer();
 
1019
  test_processIncoming_createFromServer();
 
1020
  test_processIncoming_reconcile();
 
1021
  test_processIncoming_fetchNum();
 
1022
  test_uploadOutgoing_toEmptyServer();
 
1023
  test_uploadOutgoing_MAX_UPLOAD_RECORDS();
 
1024
  test_syncFinish_noDelete();
 
1025
  test_syncFinish_deleteByIds();
 
1026
  test_syncFinish_deleteLotsInBatches();
 
1027
}