Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 Cu.import("resource://gre/modules/PlacesUtils.jsm");
5 Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
6 Cu.import("resource://services-common/async.js");
7 Cu.import("resource://gre/modules/Log.jsm");
8 Cu.import("resource://services-sync/engines.js");
9 Cu.import("resource://services-sync/engines/bookmarks.js");
10 Cu.import("resource://services-sync/service.js");
11 Cu.import("resource://services-sync/util.js");
12 Cu.import("resource://testing-common/services/sync/utils.js");
13 Cu.import("resource://gre/modules/Promise.jsm");
15 Service.engineManager.register(BookmarksEngine);
17 add_test(function bad_record_allIDs() {
18 let server = new SyncServer();
19 server.start();
20 let syncTesting = new SyncTestingInfrastructure(server.server);
22 _("Ensure that bad Places queries don't cause an error in getAllIDs.");
23 let engine = new BookmarksEngine(Service);
24 let store = engine._store;
25 let badRecordID = PlacesUtils.bookmarks.insertBookmark(
26 PlacesUtils.bookmarks.toolbarFolder,
27 Utils.makeURI("place:folder=1138"),
28 PlacesUtils.bookmarks.DEFAULT_INDEX,
29 null);
31 do_check_true(badRecordID > 0);
32 _("Record is " + badRecordID);
33 _("Type: " + PlacesUtils.bookmarks.getItemType(badRecordID));
35 _("Fetching children.");
36 store._getChildren("toolbar", {});
38 _("Fetching all IDs.");
39 let all = store.getAllIDs();
41 _("All IDs: " + JSON.stringify(all));
42 do_check_true("menu" in all);
43 do_check_true("toolbar" in all);
45 _("Clean up.");
46 PlacesUtils.bookmarks.removeItem(badRecordID);
47 server.stop(run_next_test);
48 });
50 add_test(function test_ID_caching() {
51 let server = new SyncServer();
52 server.start();
53 let syncTesting = new SyncTestingInfrastructure(server.server);
55 _("Ensure that Places IDs are not cached.");
56 let engine = new BookmarksEngine(Service);
57 let store = engine._store;
58 _("All IDs: " + JSON.stringify(store.getAllIDs()));
60 let mobileID = store.idForGUID("mobile");
61 _("Change the GUID for that item, and drop the mobile anno.");
62 store._setGUID(mobileID, "abcdefghijkl");
63 PlacesUtils.annotations.removeItemAnnotation(mobileID, "mobile/bookmarksRoot");
65 let err;
66 let newMobileID;
68 // With noCreate, we don't find an entry.
69 try {
70 newMobileID = store.idForGUID("mobile", true);
71 _("New mobile ID: " + newMobileID);
72 } catch (ex) {
73 err = ex;
74 _("Error: " + Utils.exceptionStr(err));
75 }
77 do_check_true(!err);
79 // With !noCreate, lookup works, and it's different.
80 newMobileID = store.idForGUID("mobile", false);
81 _("New mobile ID: " + newMobileID);
82 do_check_true(!!newMobileID);
83 do_check_neq(newMobileID, mobileID);
85 // And it's repeatable, even with creation enabled.
86 do_check_eq(newMobileID, store.idForGUID("mobile", false));
88 do_check_eq(store.GUIDForId(mobileID), "abcdefghijkl");
89 server.stop(run_next_test);
90 });
92 function serverForFoo(engine) {
93 return serverForUsers({"foo": "password"}, {
94 meta: {global: {engines: {bookmarks: {version: engine.version,
95 syncID: engine.syncID}}}},
96 bookmarks: {}
97 });
98 }
100 add_test(function test_processIncoming_error_orderChildren() {
101 _("Ensure that _orderChildren() is called even when _processIncoming() throws an error.");
103 let engine = new BookmarksEngine(Service);
104 let store = engine._store;
105 let server = serverForFoo(engine);
106 new SyncTestingInfrastructure(server.server);
108 let collection = server.user("foo").collection("bookmarks");
110 try {
112 let folder1_id = PlacesUtils.bookmarks.createFolder(
113 PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
114 let folder1_guid = store.GUIDForId(folder1_id);
116 let fxuri = Utils.makeURI("http://getfirefox.com/");
117 let tburi = Utils.makeURI("http://getthunderbird.com/");
119 let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
120 folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
121 let bmk1_guid = store.GUIDForId(bmk1_id);
122 let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
123 folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
124 let bmk2_guid = store.GUIDForId(bmk2_id);
126 // Create a server record for folder1 where we flip the order of
127 // the children.
128 let folder1_payload = store.createRecord(folder1_guid).cleartext;
129 folder1_payload.children.reverse();
130 collection.insert(folder1_guid, encryptPayload(folder1_payload));
132 // Create a bogus record that when synced down will provoke a
133 // network error which in turn provokes an exception in _processIncoming.
134 const BOGUS_GUID = "zzzzzzzzzzzz";
135 let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!");
136 bogus_record.get = function get() {
137 throw "Sync this!";
138 };
140 // Make the 10 minutes old so it will only be synced in the toFetch phase.
141 bogus_record.modified = Date.now() / 1000 - 60 * 10;
142 engine.lastSync = Date.now() / 1000 - 60;
143 engine.toFetch = [BOGUS_GUID];
145 let error;
146 try {
147 engine.sync();
148 } catch(ex) {
149 error = ex;
150 }
151 do_check_true(!!error);
153 // Verify that the bookmark order has been applied.
154 let new_children = store.createRecord(folder1_guid).children;
155 do_check_eq(new_children.length, 2);
156 do_check_eq(new_children[0], folder1_payload.children[0]);
157 do_check_eq(new_children[1], folder1_payload.children[1]);
159 do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk1_id), 1);
160 do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk2_id), 0);
162 } finally {
163 store.wipe();
164 Svc.Prefs.resetBranch("");
165 Service.recordManager.clearCache();
166 server.stop(run_next_test);
167 }
168 });
170 add_task(function test_restorePromptsReupload() {
171 _("Ensure that restoring from a backup will reupload all records.");
172 let engine = new BookmarksEngine(Service);
173 let store = engine._store;
174 let server = serverForFoo(engine);
175 new SyncTestingInfrastructure(server.server);
177 let collection = server.user("foo").collection("bookmarks");
179 Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup...
181 try {
183 let folder1_id = PlacesUtils.bookmarks.createFolder(
184 PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
185 let folder1_guid = store.GUIDForId(folder1_id);
186 _("Folder 1: " + folder1_id + ", " + folder1_guid);
188 let fxuri = Utils.makeURI("http://getfirefox.com/");
189 let tburi = Utils.makeURI("http://getthunderbird.com/");
191 _("Create a single record.");
192 let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
193 folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
194 let bmk1_guid = store.GUIDForId(bmk1_id);
195 _("Get Firefox!: " + bmk1_id + ", " + bmk1_guid);
198 let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
199 .getService(Ci.nsIProperties);
201 let backupFile = dirSvc.get("TmpD", Ci.nsILocalFile);
203 _("Make a backup.");
204 backupFile.append("t_b_e_" + Date.now() + ".json");
206 _("Backing up to file " + backupFile.path);
207 backupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
208 yield BookmarkJSONUtils.exportToFile(backupFile);
210 _("Create a different record and sync.");
211 let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
212 folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
213 let bmk2_guid = store.GUIDForId(bmk2_id);
214 _("Get Thunderbird!: " + bmk2_id + ", " + bmk2_guid);
216 PlacesUtils.bookmarks.removeItem(bmk1_id);
218 let error;
219 try {
220 engine.sync();
221 } catch(ex) {
222 error = ex;
223 _("Got error: " + Utils.exceptionStr(ex));
224 }
225 do_check_true(!error);
227 _("Verify that there's only one bookmark on the server, and it's Thunderbird.");
228 // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
229 let wbos = collection.keys(function (id) {
230 return ["menu", "toolbar", "mobile", folder1_guid].indexOf(id) == -1;
231 });
232 do_check_eq(wbos.length, 1);
233 do_check_eq(wbos[0], bmk2_guid);
235 _("Now restore from a backup.");
236 yield BookmarkJSONUtils.importFromFile(backupFile, true);
238 _("Ensure we have the bookmarks we expect locally.");
239 let guids = store.getAllIDs();
240 _("GUIDs: " + JSON.stringify(guids));
241 let found = false;
242 let count = 0;
243 let newFX;
244 for (let guid in guids) {
245 count++;
246 let id = store.idForGUID(guid, true);
247 // Only one bookmark, so _all_ should be Firefox!
248 if (PlacesUtils.bookmarks.getItemType(id) == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
249 let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
250 _("Found URI " + uri.spec + " for GUID " + guid);
251 do_check_eq(uri.spec, fxuri.spec);
252 newFX = guid; // Save the new GUID after restore.
253 found = true; // Only runs if the above check passes.
254 }
255 }
256 _("We found it: " + found);
257 do_check_true(found);
259 _("Have the correct number of IDs locally, too.");
260 do_check_eq(count, ["menu", "toolbar", folder1_id, bmk1_id].length);
262 _("Sync again. This'll wipe bookmarks from the server.");
263 try {
264 engine.sync();
265 } catch(ex) {
266 error = ex;
267 _("Got error: " + Utils.exceptionStr(ex));
268 }
269 do_check_true(!error);
271 _("Verify that there's only one bookmark on the server, and it's Firefox.");
272 // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
273 let payloads = server.user("foo").collection("bookmarks").payloads();
274 let bookmarkWBOs = payloads.filter(function (wbo) {
275 return wbo.type == "bookmark";
276 });
277 let folderWBOs = payloads.filter(function (wbo) {
278 return ((wbo.type == "folder") &&
279 (wbo.id != "menu") &&
280 (wbo.id != "toolbar"));
281 });
283 do_check_eq(bookmarkWBOs.length, 1);
284 do_check_eq(bookmarkWBOs[0].id, newFX);
285 do_check_eq(bookmarkWBOs[0].bmkUri, fxuri.spec);
286 do_check_eq(bookmarkWBOs[0].title, "Get Firefox!");
288 _("Our old friend Folder 1 is still in play.");
289 do_check_eq(folderWBOs.length, 1);
290 do_check_eq(folderWBOs[0].title, "Folder 1");
292 } finally {
293 store.wipe();
294 Svc.Prefs.resetBranch("");
295 Service.recordManager.clearCache();
296 let deferred = Promise.defer();
297 server.stop(deferred.resolve);
298 yield deferred.promise;
299 }
300 });
302 function FakeRecord(constructor, r) {
303 constructor.call(this, "bookmarks", r.id);
304 for (let x in r) {
305 this[x] = r[x];
306 }
307 }
309 // Bug 632287.
310 add_test(function test_mismatched_types() {
311 _("Ensure that handling a record that changes type causes deletion " +
312 "then re-adding.");
314 let oldRecord = {
315 "id": "l1nZZXfB8nC7",
316 "type":"folder",
317 "parentName":"Bookmarks Toolbar",
318 "title":"Innerst i Sneglehode",
319 "description":null,
320 "parentid": "toolbar"
321 };
323 let newRecord = {
324 "id": "l1nZZXfB8nC7",
325 "type":"livemark",
326 "siteUri":"http://sneglehode.wordpress.com/",
327 "feedUri":"http://sneglehode.wordpress.com/feed/",
328 "parentName":"Bookmarks Toolbar",
329 "title":"Innerst i Sneglehode",
330 "description":null,
331 "children":
332 ["HCRq40Rnxhrd", "YeyWCV1RVsYw", "GCceVZMhvMbP", "sYi2hevdArlF",
333 "vjbZlPlSyGY8", "UtjUhVyrpeG6", "rVq8WMG2wfZI", "Lx0tcy43ZKhZ",
334 "oT74WwV8_j4P", "IztsItWVSo3-"],
335 "parentid": "toolbar"
336 };
338 let engine = new BookmarksEngine(Service);
339 let store = engine._store;
340 let server = serverForFoo(engine);
341 new SyncTestingInfrastructure(server.server);
343 _("GUID: " + store.GUIDForId(6, true));
345 try {
346 let bms = PlacesUtils.bookmarks;
347 let oldR = new FakeRecord(BookmarkFolder, oldRecord);
348 let newR = new FakeRecord(Livemark, newRecord);
349 oldR._parent = PlacesUtils.bookmarks.toolbarFolder;
350 newR._parent = PlacesUtils.bookmarks.toolbarFolder;
352 store.applyIncoming(oldR);
353 _("Applied old. It's a folder.");
354 let oldID = store.idForGUID(oldR.id);
355 _("Old ID: " + oldID);
356 do_check_eq(bms.getItemType(oldID), bms.TYPE_FOLDER);
357 do_check_false(PlacesUtils.annotations
358 .itemHasAnnotation(oldID, PlacesUtils.LMANNO_FEEDURI));
360 store.applyIncoming(newR);
361 let newID = store.idForGUID(newR.id);
362 _("New ID: " + newID);
364 _("Applied new. It's a livemark.");
365 do_check_eq(bms.getItemType(newID), bms.TYPE_FOLDER);
366 do_check_true(PlacesUtils.annotations
367 .itemHasAnnotation(newID, PlacesUtils.LMANNO_FEEDURI));
369 } finally {
370 store.wipe();
371 Svc.Prefs.resetBranch("");
372 Service.recordManager.clearCache();
373 server.stop(run_next_test);
374 }
375 });
377 add_test(function test_bookmark_guidMap_fail() {
378 _("Ensure that failures building the GUID map cause early death.");
380 let engine = new BookmarksEngine(Service);
381 let store = engine._store;
383 let store = engine._store;
384 let server = serverForFoo(engine);
385 let coll = server.user("foo").collection("bookmarks");
386 new SyncTestingInfrastructure(server.server);
388 // Add one item to the server.
389 let itemID = PlacesUtils.bookmarks.createFolder(
390 PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
391 let itemGUID = store.GUIDForId(itemID);
392 let itemPayload = store.createRecord(itemGUID).cleartext;
393 coll.insert(itemGUID, encryptPayload(itemPayload));
395 engine.lastSync = 1; // So we don't back up.
397 // Make building the GUID map fail.
398 store.getAllIDs = function () { throw "Nooo"; };
400 // Ensure that we throw when accessing _guidMap.
401 engine._syncStartup();
402 _("No error.");
403 do_check_false(engine._guidMapFailed);
405 _("We get an error if building _guidMap fails in use.");
406 let err;
407 try {
408 _(engine._guidMap);
409 } catch (ex) {
410 err = ex;
411 }
412 do_check_eq(err.code, Engine.prototype.eEngineAbortApplyIncoming);
413 do_check_eq(err.cause, "Nooo");
415 _("We get an error and abort during processIncoming.");
416 err = undefined;
417 try {
418 engine._processIncoming();
419 } catch (ex) {
420 err = ex;
421 }
422 do_check_eq(err, "Nooo");
424 server.stop(run_next_test);
425 });
427 add_test(function test_bookmark_is_taggable() {
428 let engine = new BookmarksEngine(Service);
429 let store = engine._store;
431 do_check_true(store.isTaggable("bookmark"));
432 do_check_true(store.isTaggable("microsummary"));
433 do_check_true(store.isTaggable("query"));
434 do_check_false(store.isTaggable("folder"));
435 do_check_false(store.isTaggable("livemark"));
436 do_check_false(store.isTaggable(null));
437 do_check_false(store.isTaggable(undefined));
438 do_check_false(store.isTaggable(""));
440 run_next_test();
441 });
443 add_test(function test_bookmark_tag_but_no_uri() {
444 _("Ensure that a bookmark record with tags, but no URI, doesn't throw an exception.");
446 let engine = new BookmarksEngine(Service);
447 let store = engine._store;
449 // We're simply checking that no exception is thrown, so
450 // no actual checks in this test.
452 store._tagURI(null, ["foo"]);
453 store._tagURI(null, null);
454 store._tagURI(Utils.makeURI("about:fake"), null);
456 let record = {
457 _parent: PlacesUtils.bookmarks.toolbarFolder,
458 id: Utils.makeGUID(),
459 description: "",
460 tags: ["foo"],
461 title: "Taggy tag",
462 type: "folder"
463 };
465 // Because update() walks the cleartext.
466 record.cleartext = record;
468 store.create(record);
469 record.tags = ["bar"];
470 store.update(record);
472 run_next_test();
473 });
475 add_test(function test_misreconciled_root() {
476 _("Ensure that we don't reconcile an arbitrary record with a root.");
478 let engine = new BookmarksEngine(Service);
479 let store = engine._store;
480 let server = serverForFoo(engine);
482 // Log real hard for this test.
483 store._log.trace = store._log.debug;
484 engine._log.trace = engine._log.debug;
486 engine._syncStartup();
488 // Let's find out where the toolbar is right now.
489 let toolbarBefore = store.createRecord("toolbar", "bookmarks");
490 let toolbarIDBefore = store.idForGUID("toolbar");
491 do_check_neq(-1, toolbarIDBefore);
493 let parentGUIDBefore = toolbarBefore.parentid;
494 let parentIDBefore = store.idForGUID(parentGUIDBefore);
495 do_check_neq(-1, parentIDBefore);
496 do_check_eq("string", typeof(parentGUIDBefore));
498 _("Current parent: " + parentGUIDBefore + " (" + parentIDBefore + ").");
500 let to_apply = {
501 id: "zzzzzzzzzzzz",
502 type: "folder",
503 title: "Bookmarks Toolbar",
504 description: "Now you're for it.",
505 parentName: "",
506 parentid: "mobile", // Why not?
507 children: [],
508 };
510 let rec = new FakeRecord(BookmarkFolder, to_apply);
511 let encrypted = encryptPayload(rec.cleartext);
512 encrypted.decrypt = function () {
513 for (let x in rec) {
514 encrypted[x] = rec[x];
515 }
516 };
518 _("Applying record.");
519 engine._processIncoming({
520 get: function () {
521 this.recordHandler(encrypted);
522 return {success: true}
523 },
524 });
526 // Ensure that afterwards, toolbar is still there.
527 // As of 2012-12-05, this only passes because Places doesn't use "toolbar" as
528 // the real GUID, instead using a generated one. Sync does the translation.
529 let toolbarAfter = store.createRecord("toolbar", "bookmarks");
530 let parentGUIDAfter = toolbarAfter.parentid;
531 let parentIDAfter = store.idForGUID(parentGUIDAfter);
532 do_check_eq(store.GUIDForId(toolbarIDBefore), "toolbar");
533 do_check_eq(parentGUIDBefore, parentGUIDAfter);
534 do_check_eq(parentIDBefore, parentIDAfter);
536 server.stop(run_next_test);
537 });
539 function run_test() {
540 initTestLogging("Trace");
541 generateNewKeys(Service.collectionKeys);
542 run_next_test();
543 }