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");
13
* A fake engine implementation.
15
* Complete with record, store, and tracker implementations.
18
function SteamRecord(uri) {
19
CryptoWrapper.call(this, uri);
21
SteamRecord.prototype = {
22
__proto__: CryptoWrapper.prototype
24
Utils.deferGetSet(SteamRecord, "cleartext", ["denomination"]);
26
function SteamStore() {
27
Store.call(this, "Steam");
30
SteamStore.prototype = {
31
__proto__: Store.prototype,
33
create: function Store_create(record) {
34
this.items[record.id] = record.denomination;
37
remove: function Store_remove(record) {
38
delete this.items[record.id];
41
update: function Store_update(record) {
42
this.items[record.id] = record.denomination;
45
itemExists: function Store_itemExists(id) {
46
return (id in this.items);
49
createRecord: function(id) {
50
var record = new SteamRecord();
52
record.denomination = this.items[id] || "Data for new record: " + id;
56
changeItemID: function(oldID, newID) {
57
this.items[newID] = this.items[oldID];
58
delete this.items[oldID];
61
getAllIDs: function() {
63
for (var id in this.items) {
74
function SteamTracker() {
75
Tracker.call(this, "Steam");
77
SteamTracker.prototype = {
78
__proto__: Tracker.prototype
82
function SteamEngine() {
83
SyncEngine.call(this, "Steam");
85
SteamEngine.prototype = {
86
__proto__: SyncEngine.prototype,
87
_storeObj: SteamStore,
88
_trackerObj: SteamTracker,
89
_recordObj: SteamRecord,
91
_findDupe: function(item) {
92
for (let [id, value] in Iterator(this._store.items)) {
93
if (item.denomination == value) {
101
function makeSteamEngine() {
102
return new SteamEngine();
105
var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
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);
122
function createAndUploadKeypair() {
123
let storageURL = Svc.Prefs.get("clusterURL") + Svc.Prefs.get("storageAPI")
124
+ "/" + ID.get("WeaveID").username + "/storage/";
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);
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);
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);
149
return {encryption: "http://localhost:8080/1.0/foo/storage/crypto/steam",
150
ciphertext: cleartext, // ciphertext == cleartext with fake crypto
152
hmac: Utils.sha256HMAC(cleartext, null)};
159
* SyncEngine._sync() is divided into four rather independent steps:
162
* - _processIncoming()
163
* - _uploadOutgoing()
166
* In the spirit of unit testing, these are tested individually for
167
* different scenarios below.
170
function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
171
_("SyncEngine._syncStartup resets sync and wipes server data if there's no or an oudated global record");
173
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
174
let crypto_steam = new ServerWBO('steam');
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"}));
185
let server = sync_httpd_setup({
186
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
187
"/1.0/foo/storage/steam": collection.handler()
189
createAndUploadKeypair();
191
let engine = makeSteamEngine();
192
engine._store.items = {rekolok: "Rekonstruktionslokomotive"};
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);
203
engine.lastSync = Date.now() / 1000;
204
engine._syncStartup();
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);
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);
216
// Bulk key was uploaded
217
do_check_true(!!crypto_steam.payload);
218
do_check_true(!!crypto_steam.data.keyring);
220
// WBO IDs are added to tracker (they're all marked for uploading)
221
do_check_eq(engine._tracker.changedIDs["rekolok"], 0);
224
server.stop(function() {});
225
Svc.Prefs.resetBranch("");
226
Records.clearCache();
227
CryptoMetas.clearCache();
228
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
232
function test_syncStartup_metaGet404() {
233
_("SyncEngine._syncStartup resets sync and wipes server data if the symmetric key is missing 404");
235
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
237
// A symmetric key with an incorrect HMAC
238
let crypto_steam = new ServerWBO("steam");
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}}});
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"}));
255
let server = sync_httpd_setup({
256
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
257
"/1.0/foo/storage/steam": collection.handler()
259
createAndUploadKeypair();
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);
268
engine.lastSync = Date.now() / 1000;
269
engine._syncStartup();
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);
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 ");
282
server.stop(function() {});
283
Svc.Prefs.resetBranch("");
284
Records.clearCache();
285
CryptoMetas.clearCache();
286
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
290
function test_syncStartup_failedMetaGet() {
291
_("SyncEngine._syncStartup non-404 failures for getting cryptometa should stop sync");
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);
301
let engine = makeSteamEngine();
304
_("Getting the cryptometa will fail and should set the appropriate failure");
307
engine._syncStartup();
311
do_check_eq(error.failureCode, ENGINE_METARECORD_DOWNLOAD_FAIL);
314
server.stop(function() {});
315
Svc.Prefs.resetBranch("");
316
Records.clearCache();
317
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
321
function test_syncStartup_serverHasNewerVersion() {
322
_("SyncEngine._syncStartup ");
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()
330
let engine = makeSteamEngine();
333
// The server has a newer version of the data and our engine can
334
// handle. That should give us an exception.
337
engine._syncStartup();
341
do_check_eq(error.failureCode, VERSION_OUT_OF_DATE);
344
server.stop(function() {});
345
Svc.Prefs.resetBranch("");
346
Records.clearCache();
347
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
352
function test_syncStartup_syncIDMismatchResetsClient() {
353
_("SyncEngine._syncStartup resets sync if syncIDs don't match");
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()
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());
368
createAndUploadKeypair();
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);
377
engine.lastSync = Date.now() / 1000;
378
engine._syncStartup();
380
// The engine has assumed the server's syncID
381
do_check_eq(engine.syncID, 'foobar');
384
do_check_eq(engine.lastSync, 0);
387
server.stop(function() {});
388
Svc.Prefs.resetBranch("");
389
Records.clearCache();
390
CryptoMetas.clearCache();
391
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
396
function test_syncStartup_badKeyWipesServerData() {
397
_("SyncEngine._syncStartup resets sync and wipes server data if there's something wrong with the symmetric key");
399
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
401
// A symmetric key with an incorrect HMAC
402
let crypto_steam = new ServerWBO('steam');
403
crypto_steam.payload = JSON.stringify({
405
"http://localhost:8080/1.0/foo/storage/keys/pubkey": {
406
wrapped: Svc.Crypto.generateRandomKey(),
407
hmac: "this-hmac-is-incorrect"
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}}});
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"}));
427
let server = sync_httpd_setup({
428
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
429
"/1.0/foo/storage/steam": collection.handler()
431
createAndUploadKeypair();
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);
442
engine.lastSync = Date.now() / 1000;
443
engine._syncStartup();
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);
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 ");
456
server.stop(function() {});
457
Svc.Prefs.resetBranch("");
458
Records.clearCache();
459
CryptoMetas.clearCache();
460
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
465
function test_processIncoming_emptyServer() {
466
_("SyncEngine._processIncoming working with an empty server backend");
468
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
469
let crypto_steam = new ServerWBO('steam');
470
let collection = new ServerCollection();
472
let server = sync_httpd_setup({
473
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
474
"/1.0/foo/storage/steam": collection.handler()
476
createAndUploadKeypair();
478
let engine = makeSteamEngine();
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);
487
server.stop(function() {});
488
Svc.Prefs.resetBranch("");
489
Records.clearCache();
490
CryptoMetas.clearCache();
491
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
496
function test_processIncoming_createFromServer() {
497
_("SyncEngine._processIncoming creates new records from server data");
499
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
500
let crypto_steam = new ServerWBO('steam');
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"}));
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()
517
createAndUploadKeypair();
518
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
520
let engine = makeSteamEngine();
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);
529
engine._processIncoming();
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);
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");
540
server.stop(function() {});
541
Svc.Prefs.resetBranch("");
542
Records.clearCache();
543
CryptoMetas.clearCache();
544
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
549
function test_processIncoming_reconcile() {
550
_("SyncEngine._processIncoming updates local records");
552
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
553
let crypto_steam = new ServerWBO('steam');
554
let collection = new ServerCollection();
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..."}));
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!"}));
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;
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!"}));
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"}));
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"}));
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!",
601
let server = sync_httpd_setup({
602
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
603
"/1.0/foo/storage/steam": collection.handler()
605
createAndUploadKeypair();
606
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
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",
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);
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);
630
engine._delete = {}; // normally set up by _syncStartup
631
engine._processIncoming();
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);
637
// The new record is created.
638
do_check_eq(engine._store.items.newrecord, "New stuff...");
640
// The 'newerserver' record is updated since the server data is newer.
641
do_check_eq(engine._store.items.newerserver, "New data!");
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);
648
// Updated with server data.
649
do_check_eq(engine._store.items.updateclient, "Get this!");
651
// The dupe with the shorter ID is kept, the longer one is slated
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);
657
// The 'nukeme' record marked as deleted is removed.
658
do_check_eq(engine._store.items.nukeme, undefined);
661
server.stop(function() {});
662
Svc.Prefs.resetBranch("");
663
Records.clearCache();
664
CryptoMetas.clearCache();
665
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
670
function test_processIncoming_fetchNum() {
671
_("SyncEngine._processIncoming doesn't fetch everything at ones on mobile clients");
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();
678
// Let's create some 234 server side records. They're all at least
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;
688
let server = sync_httpd_setup({
689
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
690
"/1.0/foo/storage/steam": collection.handler()
692
createAndUploadKeypair();
693
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
695
let engine = makeSteamEngine();
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);
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);
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;
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;
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);
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;
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);
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();
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);
766
server.stop(function() {});
767
Svc.Prefs.resetBranch("");
768
Records.clearCache();
769
CryptoMetas.clearCache();
770
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
775
function test_uploadOutgoing_toEmptyServer() {
776
_("SyncEngine._uploadOutgoing uploads new records to server");
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');
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()
790
createAndUploadKeypair();
791
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
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);
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);
806
engine._uploadOutgoing();
808
// Ensure the marked record ('scotsman') has been uploaded and is
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,
814
do_check_eq(engine._tracker.changedIDs['scotsman'], undefined);
816
// The 'flying' record wasn't marked so it wasn't uploaded
817
do_check_eq(collection.wbos.flying.payload, undefined);
820
server.stop(function() {});
821
Svc.Prefs.resetBranch("");
822
Records.clearCache();
823
CryptoMetas.clearCache();
824
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
829
function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
830
_("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_RECORDS");
832
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
833
let crypto_steam = new ServerWBO('steam');
834
let collection = new ServerCollection();
836
// Let's count how many times the client posts to the server
838
collection.post = (function(orig) {
841
return orig.apply(this, arguments);
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);
854
let server = sync_httpd_setup({
855
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
856
"/1.0/foo/storage/steam": collection.handler()
858
createAndUploadKeypair();
859
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
863
// Confirm initial environment
864
do_check_eq(noOfUploads, 0);
866
engine._uploadOutgoing();
868
// Ensure all records have been uploaded
869
for (i = 0; i < 234; i++) {
870
do_check_true(!!collection.wbos['record-no-'+i].payload);
873
// Ensure that the uploads were performed in batches of MAX_UPLOAD_RECORDS
874
do_check_eq(noOfUploads, Math.ceil(234/MAX_UPLOAD_RECORDS));
877
server.stop(function() {});
878
Svc.Prefs.resetBranch("");
879
Records.clearCache();
880
CryptoMetas.clearCache();
881
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
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;
892
// _syncFinish() will reset the engine's score.
893
engine._syncFinish();
894
do_check_eq(engine.score, 0);
898
function test_syncFinish_deleteByIds() {
899
_("SyncEngine._syncFinish deletes server records slated for deletion (list of record IDs).");
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"}));
913
let server = httpd_setup({
914
"/1.0/foo/storage/steam": collection.handler()
917
let engine = makeSteamEngine();
919
engine._delete = {ids: ['flying', 'rekolok']};
920
engine._syncFinish();
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);
928
// The deletion todo list has been reset.
929
do_check_eq(engine._delete.ids, undefined);
932
server.stop(function() {});
933
Svc.Prefs.resetBranch("");
934
Records.clearCache();
935
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
940
function test_syncFinish_deleteLotsInBatches() {
941
_("SyncEngine._syncFinish deletes server records in batches of 100 (list of record IDs).");
943
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
944
let collection = new ServerCollection();
946
// Let's count how many times the client does a DELETE request to the server
948
collection.delete = (function(orig) {
951
return orig.apply(this, arguments);
953
}(collection.delete));
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;
965
let server = httpd_setup({
966
"/1.0/foo/storage/steam": collection.handler()
969
let engine = makeSteamEngine();
972
// Confirm initial environment
973
do_check_eq(noOfUploads, 0);
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);
984
engine._syncFinish();
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);
993
do_check_true(!!collection.wbos[id].payload);
997
// The deletion was done in batches
998
do_check_eq(noOfUploads, 2 + 1);
1000
// The deletion todo list has been reset.
1001
do_check_eq(engine._delete.ids, undefined);
1004
server.stop(function() {});
1005
Svc.Prefs.resetBranch("");
1006
Records.clearCache();
1007
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
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();