function checkRecordedEvents(object, expected, message) { // Ignore event telemetry from the merger.
let checkEvents = recordedEvents.filter(event => event.object == object);
deepEqual(checkEvents, expected, message); // and clear the list so future checks are easier to write.
recordedEvents = [];
}
async function fetchAllRecordIds() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached(`
WITH RECURSIVE
syncedItems(id, guid) AS (
SELECT b.id, b.guid FROM moz_bookmarks b
WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____', 'mobile______')
UNION ALL
SELECT b.id, b.guid FROM moz_bookmarks b
JOIN syncedItems s ON b.parent = s.id
)
SELECT guid FROM syncedItems`);
let recordIds = new Set(); for (let row of rows) {
let recordId = PlacesSyncUtils.bookmarks.guidToRecordId(
row.getResultByName("guid")
);
recordIds.add(recordId);
} return recordIds;
}
async function cleanupEngine(engine) {
await engine.resetClient();
await engine._store.wipe(); for (const pref of Svc.PrefBranch.getChildList("")) {
Svc.PrefBranch.clearUserPref(pref);
}
Service.recordManager.clearCache(); // Note we don't finalize the engine here as add_bookmark_test() does.
}
async function cleanup(engine, server) {
await promiseStopServer(server);
await cleanupEngine(engine);
}
add_task(async function setup() {
await generateNewKeys(Service.collectionKeys);
await Service.engineManager.unregister("bookmarks");
Service.recordTelemetryEvent = (object, method, value, extra = undefined) => {
recordedEvents.push({ object, method, value, extra });
};
});
add_task(async function test_buffer_timeout() {
await Service.recordManager.clearCache();
await PlacesSyncUtils.bookmarks.reset();
let engine = new BookmarksEngine(Service);
engine._newWatchdog = function () { // Return an already-aborted watchdog, so that we can abort merges // immediately.
let watchdog = Async.watchdog();
watchdog.controller.abort(); return watchdog;
};
await engine.initialize();
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
info("We expect this sync to fail");
await Assert.rejects(
sync_engine_and_validate_telem(engine, true),
ex => ex.name == "InterruptedError"
);
} finally {
await cleanup(engine, server);
await engine.finalize();
}
});
add_bookmark_test(async function test_maintenance_after_failure(engine) {
_("Ensure we try to run maintenance if the engine fails to sync");
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
try {
let syncStartup = engine._syncStartup;
let syncError = new Error("Something is rotten in the state of Places");
engine._syncStartup = function () { throw syncError;
};
_("Ensure the sync fails and we run maintenance");
await Assert.rejects(
sync_engine_and_validate_telem(engine, true),
ex => ex == syncError
);
checkRecordedEvents( "maintenance",
[
{
object: "maintenance",
method: "run",
value: "bookmarks",
extra: undefined,
},
], "Should record event for first maintenance run"
);
_("Sync again, but ensure maintenance doesn't run");
await Assert.rejects(
sync_engine_and_validate_telem(engine, true),
ex => ex == syncError
);
checkRecordedEvents( "maintenance",
[], "Should not record event if maintenance didn't run"
);
_("Fast-forward last maintenance pref; ensure maintenance runs");
Services.prefs.setIntPref( "places.database.lastMaintenance",
Date.now() / 1000 - 14400
);
await Assert.rejects(
sync_engine_and_validate_telem(engine, true),
ex => ex == syncError
);
checkRecordedEvents( "maintenance",
[
{
object: "maintenance",
method: "run",
value: "bookmarks",
extra: undefined,
},
], "Should record event for second maintenance run"
);
_("Fix sync failure; ensure we report success after maintenance");
engine._syncStartup = syncStartup;
await sync_engine_and_validate_telem(engine, false);
checkRecordedEvents( "maintenance",
[
{
object: "maintenance",
method: "fix",
value: "bookmarks",
extra: undefined,
},
], "Should record event for successful sync after second maintenance"
);
await sync_engine_and_validate_telem(engine, false);
checkRecordedEvents( "maintenance",
[], "Should not record maintenance events after successful sync"
);
} finally {
await cleanup(engine, server);
}
});
add_bookmark_test(async function test_delete_invalid_roots_from_server(engine) {
_("Ensure that we delete the Places and Reading List roots from the server.");
enableValidationPrefs();
let store = engine._store;
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
engine._tracker.start();
try {
let placesRecord = await store.createRecord("places");
collection.insert("places", encryptPayload(placesRecord.cleartext));
let listBmk = new Bookmark("bookmarks", Utils.makeGUID());
listBmk.bmkUri = "https://example.com";
listBmk.title = "Example reading list entry";
listBmk.parentName = "Reading List";
listBmk.parentid = "readinglist";
collection.insert(listBmk.id, encryptPayload(listBmk.cleartext));
// Note that we don't insert a record for the toolbar, so the engine will // report a parent-child disagreement, since Firefox's `parentid` is // `toolbar`.
let newBmk = new Bookmark("bookmarks", Utils.makeGUID());
newBmk.bmkUri = "http://getfirefox.com";
newBmk.title = "Get Firefox!";
newBmk.parentName = "Bookmarks Toolbar";
newBmk.parentid = "toolbar";
collection.insert(newBmk.id, encryptPayload(newBmk.cleartext));
deepEqual(
collection.keys().sort(),
["places", "readinglist", listBmk.id, newBmk.id].sort(), "Should store Places root, reading list items, and new bookmark on server"
);
let ping = await sync_engine_and_validate_telem(engine, true); // In a real sync, the engine is named `bookmarks-buffered`. // However, `sync_engine_and_validate_telem` simulates a sync where // the engine isn't registered with the engine manager, so the recorder // doesn't see its `overrideTelemetryName`.
let engineData = ping.engines.find(e => e.name == "bookmarks");
ok(engineData.validation, "Bookmarks engine should always run validation");
equal(
engineData.validation.checked,
6, "Bookmarks engine should validate all items"
);
deepEqual(
engineData.validation.problems,
[
{
name: "parentChildDisagreements",
count: 1,
},
], "Bookmarks engine should report parent-child disagreement"
);
deepEqual(
engineData.steps.map(step => step.name),
[ "fetchLocalTree", "fetchRemoteTree", "merge", "apply", "notifyObservers", "fetchLocalChangeRecords",
], "Bookmarks engine should report all merge steps"
);
deepEqual(
collection.keys().sort(),
["menu", "mobile", "toolbar", "unfiled", newBmk.id].sort(), "Should remove Places root and reading list items from server; upload local roots"
);
} finally {
await cleanup(engine, server);
}
});
add_bookmark_test(
async function test_processIncoming_error_orderChildren(engine) {
_( "Ensure that _orderChildren() is called even when _processIncoming() throws an error."
);
let store = engine._store;
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
let toolbar_record = await store.createRecord("toolbar");
collection.insert("toolbar", encryptPayload(toolbar_record.cleartext));
let bmk1_record = await store.createRecord(bmk1.guid);
collection.insert(bmk1.guid, encryptPayload(bmk1_record.cleartext));
let bmk2_record = await store.createRecord(bmk2.guid);
collection.insert(bmk2.guid, encryptPayload(bmk2_record.cleartext));
// Create a server record for folder1 where we flip the order of // the children.
let folder1_record = await store.createRecord(folder1.guid);
let folder1_payload = folder1_record.cleartext;
folder1_payload.children.reverse();
collection.insert(folder1.guid, encryptPayload(folder1_payload));
// Create a bogus record that when synced down will provoke a // network error which in turn provokes an exception in _processIncoming. const BOGUS_GUID = "zzzzzzzzzzzz";
let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!");
bogus_record.get = function get() { thrownew Error("Sync this!");
};
// Make the 10 minutes old so it will only be synced in the toFetch phase.
bogus_record.modified = new_timestamp() - 60 * 10;
await engine.setLastSync(new_timestamp() - 60);
engine.toFetch = new SerializableSet([BOGUS_GUID]);
// Verify that the bookmark order has been applied.
folder1_record = await store.createRecord(folder1.guid);
let new_children = folder1_record.children; Assert.deepEqual(
new_children.sort(),
[folder1_payload.children[0], folder1_payload.children[1]].sort()
);
// Test a JSON restore or HTML import. Use JSON if `replace` is `true`, or // HTML otherwise.
async function test_restoreOrImport(engine, { replace }) {
let verb = replace ? "restore" : "import";
let verbing = replace ? "restoring" : "importing";
let bookmarkUtils = replace ? BookmarkJSONUtils : BookmarkHTMLUtils;
_(`Ensure that ${verbing} from a backup will reupload all records.`);
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
engine._tracker.start(); // We skip usual startup...
_("Create a single record.");
let bmk1 = await PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
url: "http://getfirefox.com/",
title: "Get Firefox!",
});
_(`Get Firefox!: ${bmk1.guid}`);
let backupFilePath = PathUtils.join(
PathUtils.tempDir,
`t_b_e_${Date.now()}.json`
);
_("Make a backup.");
await bookmarkUtils.exportToFile(backupFilePath);
_("Create a different record and sync.");
let bmk2 = await PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
url: "http://getthunderbird.com/",
title: "Get Thunderbird!",
});
_(`Get Thunderbird!: ${bmk2.guid}`);
_( "Verify that there's only one bookmark on the server, and it's Thunderbird."
); // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
let wbos = collection.keys(function (id) { return !["menu", "toolbar", "mobile", "unfiled", folder1.guid].includes(
id
);
}); Assert.equal(wbos.length, 1); Assert.equal(wbos[0], bmk2.guid);
_(`Now ${verb} from a backup.`);
await bookmarkUtils.importFromFile(backupFilePath, { replace });
// If `replace` is `true`, we'll wipe the server on the next sync.
let bookmarksCollection = server.user("foo").collection("bookmarks");
_("Verify that we didn't wipe the server."); Assert.ok(!!bookmarksCollection);
_("Ensure we have the bookmarks we expect locally.");
let recordIds = await fetchAllRecordIds();
_("GUIDs: " + JSON.stringify([...recordIds]));
let bookmarkRecordIds = new Map();
let count = 0; for (let recordId of recordIds) {
count++;
let info = await PlacesUtils.bookmarks.fetch(
PlacesSyncUtils.bookmarks.recordIdToGuid(recordId)
); // Only one bookmark, so _all_ should be Firefox! if (info.type == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
_(`Found URI ${info.url.href} for record ID ${recordId}`);
bookmarkRecordIds.set(info.url.href, recordId);
}
} Assert.ok(bookmarkRecordIds.has("http://getfirefox.com/")); if (!replace) { Assert.ok(bookmarkRecordIds.has("http://getthunderbird.com/"));
}
_("Have the correct number of IDs locally, too.");
let expectedResults = [ "menu", "toolbar", "mobile", "unfiled",
folder1.guid,
bmk1.guid,
]; if (!replace) {
expectedResults.push("toolbar", folder1.guid, bmk2.guid);
} Assert.equal(count, expectedResults.length);
_("Verify that there's the right bookmarks on the server."); // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
let payloads = server.user("foo").collection("bookmarks").payloads();
let bookmarkWBOs = payloads.filter(function (wbo) { return wbo.type == "bookmark";
});
function doCheckWBOs(WBOs, expected) { Assert.equal(WBOs.length, expected.length); for (let i = 0; i < expected.length; i++) {
let lhs = WBOs[i];
let rhs = expected[i]; if ("id" in rhs) { Assert.equal(lhs.id, rhs.id);
} if ("bmkUri" in rhs) { Assert.equal(lhs.bmkUri, rhs.bmkUri);
} if ("title" in rhs) { Assert.equal(lhs.title, rhs.title);
}
}
}
function FakeRecord(constructor, r) { this.defaultCleartext = constructor.prototype.defaultCleartext;
constructor.call(this, "bookmarks", r.id); for (let x in r) { this[x] = r[x];
} // Borrow the constructor's conversion functions. this.toSyncBookmark = constructor.prototype.toSyncBookmark; this.cleartextToString = constructor.prototype.cleartextToString;
}
// Bug 632287. // (Note that `test_mismatched_folder_types()` in // toolkit/components/places/tests/sync/test_bookmark_kinds.js is an exact // copy of this test, so it's fine to remove it as part of bug 1449730)
add_task(async function test_mismatched_types() {
_( "Ensure that handling a record that changes type causes deletion " + "then re-adding."
);
let oldRecord = {
id: "l1nZZXfB8nC7",
type: "folder",
parentName: "Bookmarks Toolbar",
title: "Innerst i Sneglehode",
description: null,
parentid: "toolbar",
};
let engine = new BookmarksEngine(Service);
await engine.initialize();
let store = engine._store;
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
try {
let oldR = new FakeRecord(BookmarkFolder, oldRecord);
let newR = new FakeRecord(Livemark, newRecord);
oldR.parentid = PlacesUtils.bookmarks.toolbarGuid;
newR.parentid = PlacesUtils.bookmarks.toolbarGuid;
await store.applyIncoming(oldR);
await engine._apply();
_("Applied old. It's a folder.");
let oldID = await PlacesTestUtils.promiseItemId(oldR.id);
_("Old ID: " + oldID);
let oldInfo = await PlacesUtils.bookmarks.fetch(oldR.id); Assert.equal(oldInfo.type, PlacesUtils.bookmarks.TYPE_FOLDER);
add_bookmark_test(async function test_misreconciled_root(engine) {
_("Ensure that we don't reconcile an arbitrary record with a root.");
let store = engine._store;
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
// Log real hard for this test.
store._log.trace = store._log.debug;
engine._log.trace = engine._log.debug;
await engine._syncStartup();
// Let's find out where the toolbar is right now.
let toolbarBefore = await store.createRecord("toolbar", "bookmarks");
let toolbarIDBefore = await PlacesTestUtils.promiseItemId(
PlacesUtils.bookmarks.toolbarGuid
); Assert.notEqual(-1, toolbarIDBefore);
let parentRecordIDBefore = toolbarBefore.parentid;
let parentGUIDBefore =
PlacesSyncUtils.bookmarks.recordIdToGuid(parentRecordIDBefore);
let parentIDBefore = await PlacesTestUtils.promiseItemId(parentGUIDBefore); Assert.equal("string", typeof parentGUIDBefore);
let rec = new FakeRecord(BookmarkFolder, to_apply);
_("Applying record.");
let countTelemetry = new SyncedRecordsTelemetry();
await store.applyIncomingBatch([rec], countTelemetry);
// Ensure that afterwards, toolbar is still there. // As of 2012-12-05, this only passes because Places doesn't use "toolbar" as // the real GUID, instead using a generated one. Sync does the translation.
let toolbarAfter = await store.createRecord("toolbar", "bookmarks");
let parentRecordIDAfter = toolbarAfter.parentid;
let parentGUIDAfter =
PlacesSyncUtils.bookmarks.recordIdToGuid(parentRecordIDAfter);
let parentIDAfter = await PlacesTestUtils.promiseItemId(parentGUIDAfter); Assert.equal(
await PlacesTestUtils.promiseItemGuid(toolbarIDBefore),
PlacesUtils.bookmarks.toolbarGuid
); Assert.equal(parentGUIDBefore, parentGUIDAfter); Assert.equal(parentIDBefore, parentIDAfter);
await cleanup(engine, server);
});
add_bookmark_test(async function test_invalid_url(engine) {
_("Ensure an incoming invalid bookmark URL causes an outgoing tombstone.");
let server = await serverForFoo(engine);
let collection = server.user("foo").collection("bookmarks");
// We should find the record now exists on the server as a tombstone.
let updated = collection.cleartext(guid); Assert.ok(updated.deleted, "record was deleted");
let local = await PlacesUtils.bookmarks.fetch(guid); Assert.deepEqual(local, null, "no local bookmark exists");
await cleanup(engine, server);
});
add_bookmark_test(async function test_sync_dateAdded(engine) {
await Service.recordManager.clearCache();
await PlacesSyncUtils.bookmarks.reset();
let store = engine._store;
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
// TODO: Avoid random orange (bug 1374599), this is only necessary // intermittently - reset the last sync date so that we'll get all bookmarks.
await engine.setLastSync(1);
engine._tracker.start(); // We skip usual startup...
// Just matters that it's in the past, not how far.
let now = Date.now();
let oneYearMS = 365 * 24 * 60 * 60 * 1000;
let record1 = await store.createRecord(item1GUID);
let record2 = await store.createRecord(item2GUID);
equal(
item1.dateAdded,
record1.dateAdded, "dateAdded in past should be synced"
);
equal(
record2.dateAdded,
item2LastModified * 1000, "dateAdded in future should be ignored in favor of last modified"
);
let record3 = await store.createRecord(item3GUID);
ok(record3.dateAdded); // Make sure it's within 24 hours of the right timestamp... This is a little // dodgey but we only really care that it's basically accurate and has the // right day. Assert.less(Math.abs(Date.now() - record3.dateAdded), 24 * 60 * 60 * 1000);
let record4 = await store.createRecord(item4GUID);
equal(
record4.dateAdded,
item4LastModified * 1000, "If no dateAdded is provided, lastModified should be used"
);
let record5 = await store.createRecord(item5GUID);
equal(
record5.dateAdded,
item5LastModified * 1000, "If no dateAdded is provided, lastModified should be used (even if it's in the future)"
);
// Update item2 and try resyncing it.
item2.dateAdded = now - 100000;
collection.insert(
item2GUID,
encryptPayload(item2.cleartext),
now / 1000 - 50
);
// Also, add a local bookmark and make sure its date added makes it up to the server
let bz = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
url: "https://bugzilla.mozilla.org/",
title: "Bugzilla",
});
// last sync did a POST, which doesn't advance its lastModified value. // Next sync of the engine doesn't hit info/collections, so lastModified // remains stale. Setting it to null side-steps that.
engine.lastModified = null;
await sync_engine_and_validate_telem(engine, false);
let newRecord2 = await store.createRecord(item2GUID);
equal(
newRecord2.dateAdded,
item2.dateAdded, "dateAdded update should work for earlier date"
);
let bzWBO = collection.cleartext(bz.guid);
ok(bzWBO.dateAdded, "Locally added dateAdded lost");
let localRecord = await store.createRecord(bz.guid);
equal(
bzWBO.dateAdded,
localRecord.dateAdded, "dateAdded should not change during upload"
);
let newerRecord2 = await store.createRecord(item2GUID);
equal(
newerRecord2.dateAdded,
newRecord2.dateAdded, "dateAdded update should be ignored for later date if we know an earlier one "
);
} finally {
await cleanup(engine, server);
}
});
add_task(async function test_buffer_hasDupe() {
await Service.recordManager.clearCache();
await PlacesSyncUtils.bookmarks.reset();
let engine = new BookmarksEngine(Service);
await engine.initialize();
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
engine._tracker.start(); // We skip usual startup... try {
let guid1 = Utils.makeGUID();
let guid2 = Utils.makeGUID();
await PlacesUtils.bookmarks.insert({
guid: guid1,
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: "https://www.example.com",
title: "example.com",
});
await PlacesUtils.bookmarks.insert({
guid: guid2,
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: "https://www.example.com",
title: "example.com",
});
await sync_engine_and_validate_telem(engine, false); // Make sure we set hasDupe on outgoing records Assert.ok(collection.payloads().every(payload => payload.hasDupe));
let tombstone = JSON.parse(
JSON.parse(collection.payload(guid1)).ciphertext
); // We shouldn't set hasDupe on tombstones. Assert.ok(tombstone.deleted); Assert.ok(!tombstone.hasDupe);
let record = JSON.parse(JSON.parse(collection.payload(guid2)).ciphertext); // We should set hasDupe on weakly uploaded records. Assert.ok(!record.deleted); Assert.ok(
record.hasDupe, "Bookmarks bookmark engine should set hasDupe for weakly uploaded records."
);
let aInfo = await PlacesUtils.bookmarks.fetch("bookmarkAAAA");
equal(
aInfo.url.href, "imap://vs@eleven.vs.solnicky.cz:993/" + "fetch%3EUID%3E/INBOX%3E56291?part=1.2&type=image/jpeg&filename=" + "invalidazPrahy.jpg", "Remote bookmark A with IMAP URL should exist locally"
);
let bPayload = collection.cleartext("bookmarkBBBB");
equal(
bPayload.bmkUri, "imap://eleven.vs.solnicky.cz:993/" + "fetch%3EUID%3E/CURRENT%3E2433?part=1.2&type=text/html&filename=" + "TomEdwards.html", "Local bookmark B with IMAP URL should exist remotely"
);
} finally {
await cleanup(engine, server);
}
});
add_task(async function test_resume_buffer() {
await Service.recordManager.clearCache();
let engine = new BookmarksEngine(Service);
await engine.initialize();
await engine._store.wipe();
await engine.resetClient();
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
engine._tracker.start(); // We skip usual startup...
const batchChunkSize = 50;
engine._store._batchChunkSize = batchChunkSize; try {
let children = [];
let timestamp = round_timestamp(Date.now()); // Add two chunks worth of records to the server for (let i = 0; i < batchChunkSize * 2; ++i) {
let cleartext = {
id: Utils.makeGUID(),
type: "bookmark",
parentid: "toolbar",
title: `Bookmark ${i}`,
parentName: "Bookmarks Toolbar",
bmkUri: `https://example.com/${i}`,
};
let wbo = collection.insert(
cleartext.id,
encryptPayload(cleartext),
timestamp + 10 * i
); // Something that is effectively random, but deterministic. // (This is just to ensure we don't accidentally start using the // sortindex again).
wbo.sortindex = 1000 + Math.round(Math.sin(i / 5) * 100);
children.push(cleartext.id);
}
// Add the parent of those records, and ensure its timestamp is the most recent.
collection.insert( "toolbar",
encryptPayload({
id: "toolbar",
type: "folder",
parentid: "places",
title: "Bookmarks Toolbar",
children,
}),
timestamp + 10 * children.length
);
// Replace applyIncomingBatch with a custom one that calls the original, // but forces it to throw on the 2nd chunk.
let origApplyIncomingBatch = engine._store.applyIncomingBatch;
engine._store.applyIncomingBatch = function (records) { if (records.length > batchChunkSize) { // Hacky way to make reading from the batchChunkSize'th record throw. delete records[batchChunkSize];
Object.defineProperty(records, batchChunkSize, {
get() { thrownew Error("D:");
},
});
} return origApplyIncomingBatch.call(this, records);
};
let caughtError;
_("We expect this to fail"); try {
await sync_engine_and_validate_telem(engine, true);
} catch (e) {
caughtError = e;
} Assert.ok(caughtError, "Expected engine.sync to throw"); Assert.equal(caughtError.message, "D:");
// The buffer subtracts one second from the actual timestamp.
let lastSync = (await engine.getLastSync()) + 1; // We poisoned the batchChunkSize'th record, so the last successfully // applied record will be batchChunkSize - 1.
let expectedLastSync = timestamp + 10 * (batchChunkSize - 1); Assert.equal(expectedLastSync, lastSync);
// Check that all the children made it onto the correct record.
let toolbarRecord = await engine._store.createRecord("toolbar"); Assert.deepEqual(toolbarRecord.children.sort(), children.sort());
} finally {
await cleanup(engine, server);
await engine.finalize();
}
});
add_bookmark_test(async function test_livemarks(engine) {
_("Ensure we replace new and existing livemarks with tombstones");
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let collection = server.user("foo").collection("bookmarks");
let now = Date.now();
deepEqual(
collection.keys().sort(),
[ "livemarkAAAA", "livemarkBBBB", "livemarkCCCC", "menu", "mobile", "toolbar", "unfiled",
], "Should store original livemark A and tombstones for B and C on server"
);
let payloads = collection.payloads();
deepEqual(
payloads.find(payload => payload.id == "menu").children,
["livemarkAAAA"], "Should keep A in menu"
);
ok(
!payloads.find(payload => payload.id == "livemarkAAAA").deleted, "Should not upload tombstone for A"
);
deepEqual(
payloads.find(payload => payload.id == "toolbar").children,
[], "Should remove B from toolbar"
);
ok(
payloads.find(payload => payload.id == "livemarkBBBB").deleted, "Should upload tombstone for B"
);
deepEqual(
payloads.find(payload => payload.id == "unfiled").children,
[], "Should remove C from unfiled"
);
ok(
payloads.find(payload => payload.id == "livemarkCCCC").deleted, "Should replace C with tombstone"
);
// First bookmark record has an unknown string field
let bmk1_record = await store.createRecord(bmk1.guid);
console.log("bmk1_record: ", bmk1_record);
bmk1_record.cleartext.unknownStrField = "an unknown field from another client";
collection.insert(bmk1.guid, encryptPayload(bmk1_record.cleartext));
// Second bookmark record as an unknown object field
let bmk2_record = await store.createRecord(bmk2.guid);
bmk2_record.cleartext.unknownObjField = {
name: "an unknown object from another client",
};
collection.insert(bmk2.guid, encryptPayload(bmk2_record.cleartext));
// Sync the two bookmarks
await sync_engine_and_validate_telem(engine, true);
// Add a folder could also have an unknown field
let folder1_record = await store.createRecord(folder1.guid);
folder1_record.cleartext.unknownStrField = "a folder could also have an unknown field!";
collection.insert(folder1.guid, encryptPayload(folder1_record.cleartext));
// sync the new updates
await engine.setLastSync(1);
await sync_engine_and_validate_telem(engine, true);
let payloads = collection.payloads(); // Validate the server has the unknown fields at the top level (and now unknownFields)
let server_bmk1 = payloads.find(payload => payload.id == bmk1.guid);
deepEqual(
server_bmk1.unknownStrField, "an unknown field from another client", "unknown fields correctly on the record"
); Assert.equal(server_bmk1.unknownFields, null);
// Check that the mirror table has unknown fields
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached(
`
SELECT guid, title, unknownFields from items WHERE guid IN
(:bmk1, :bmk2, :folder1)`,
{ bmk1: bmk1.guid, bmk2: bmk2.guid, folder1: folder1.guid }
); // We should have 3 rows that came from the server Assert.equal(rows.length, 3);
// Bookmark 1 - unknown string field
let remote_bmk1 = rows.find(
row => row.getResultByName("guid") == bmk1.guid
); Assert.equal(remote_bmk1.getResultByName("title"), "Get Firefox!");
deepEqual(JSON.parse(remote_bmk1.getResultByName("unknownFields")), {
unknownStrField: "an unknown field from another client",
});
// Bookmark 2 - unknown object field
let remote_bmk2 = rows.find(
row => row.getResultByName("guid") == bmk2.guid
); Assert.equal(remote_bmk2.getResultByName("title"), "Get Thunderbird!");
deepEqual(JSON.parse(remote_bmk2.getResultByName("unknownFields")), {
unknownObjField: {
name: "an unknown object from another client",
},
});
// Folder with unknown field
// check the server still has the unknown field
deepEqual(
payloads.find(payload => payload.id == folder1.guid).unknownStrField, "a folder could also have an unknown field!", "Server still has the unknown field"
);
let remote_folder = rows.find(
row => row.getResultByName("guid") == folder1.guid
); Assert.equal(remote_folder.getResultByName("title"), "Folder 1");
deepEqual(JSON.parse(remote_folder.getResultByName("unknownFields")), {
unknownStrField: "a folder could also have an unknown field!",
});
} finally {
await cleanup(engine, server);
}
});
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.