1602 lines
40 KiB
JavaScript
1602 lines
40 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
add_task(async function test_complex_orphaning() {
|
|
let now = Date.now();
|
|
|
|
let mergeTelemetryCounts;
|
|
let buf = await openMirror("complex_orphaning", {
|
|
recordStepTelemetry(name, took, counts) {
|
|
if (name == "merge") {
|
|
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
|
|
}
|
|
},
|
|
});
|
|
|
|
// On iOS, the mirror exists as a separate table. On Desktop, we have a
|
|
// shadow mirror of synced local bookmarks without new changes.
|
|
info("Set up mirror: ((Toolbar > A > B) (Menu > G > C > D))");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.toolbarGuid,
|
|
children: [
|
|
{
|
|
guid: "folderAAAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "A",
|
|
children: [
|
|
{
|
|
guid: "folderBBBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "B",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
shuffle([
|
|
{
|
|
id: "toolbar",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderAAAAAA"],
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
parentid: "toolbar",
|
|
type: "folder",
|
|
title: "A",
|
|
children: ["folderBBBBBB"],
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
parentid: "folderAAAAAA",
|
|
type: "folder",
|
|
title: "B",
|
|
},
|
|
]),
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "folderGGGGGG",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "G",
|
|
children: [
|
|
{
|
|
guid: "folderCCCCCC",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "C",
|
|
children: [
|
|
{
|
|
guid: "folderDDDDDD",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "D",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
shuffle([
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderGGGGGG"],
|
|
},
|
|
{
|
|
id: "folderGGGGGG",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "G",
|
|
children: ["folderCCCCCC"],
|
|
},
|
|
{
|
|
id: "folderCCCCCC",
|
|
parentid: "folderGGGGGG",
|
|
type: "folder",
|
|
title: "C",
|
|
children: ["folderDDDDDD"],
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "folderCCCCCC",
|
|
type: "folder",
|
|
title: "D",
|
|
},
|
|
]),
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
info("Make local changes: delete D, add B > E");
|
|
await PlacesUtils.bookmarks.remove("folderDDDDDD");
|
|
await PlacesUtils.bookmarks.insert({
|
|
guid: "bookmarkEEEE",
|
|
parentGuid: "folderBBBBBB",
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
});
|
|
|
|
info("Make remote changes: delete B, add D > F");
|
|
await storeRecords(
|
|
buf,
|
|
shuffle([
|
|
{
|
|
id: "folderBBBBBB",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
parentid: "toolbar",
|
|
type: "folder",
|
|
title: "A",
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "folderCCCCCC",
|
|
type: "folder",
|
|
children: ["bookmarkFFFF"],
|
|
},
|
|
{
|
|
id: "bookmarkFFFF",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "F",
|
|
bmkUri: "http://example.com/f",
|
|
},
|
|
])
|
|
);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
["bookmarkFFFF", "folderAAAAAA", "folderDDDDDD"],
|
|
"Should leave deleted D; A and F with new remote structure unmerged"
|
|
);
|
|
deepEqual(
|
|
mergeTelemetryCounts,
|
|
[
|
|
{ name: "items", count: 10 },
|
|
{ name: "localDeletes", count: 1 },
|
|
{ name: "remoteDeletes", count: 1 },
|
|
],
|
|
"Should record telemetry with structure change counts"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: ["bookmarkEEEE", "bookmarkFFFF", "folderAAAAAA", "folderCCCCCC"],
|
|
deleted: ["folderDDDDDD"],
|
|
},
|
|
"Should upload new records for (A > E), (C > F); tombstone for D"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.rootGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.rootGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "",
|
|
children: [
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "folderGGGGGG",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "G",
|
|
children: [
|
|
{
|
|
guid: "folderCCCCCC",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "C",
|
|
children: [
|
|
{
|
|
// D was deleted, so F moved to C, the closest surviving parent.
|
|
guid: "bookmarkFFFF",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "F",
|
|
url: "http://example.com/f",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.toolbarGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 1,
|
|
title: BookmarksToolbarTitle,
|
|
children: [
|
|
{
|
|
guid: "folderAAAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "A",
|
|
children: [
|
|
{
|
|
// B was deleted, so E moved to A.
|
|
guid: "bookmarkEEEE",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.unfiledGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 3,
|
|
title: UnfiledBookmarksTitle,
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.mobileGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 4,
|
|
title: MobileBookmarksTitle,
|
|
},
|
|
],
|
|
},
|
|
"Should move orphans to closest surviving parent"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(
|
|
tombstones.map(({ guid }) => guid),
|
|
["folderDDDDDD"],
|
|
"Should store local tombstone for D"
|
|
);
|
|
Assert.ok(
|
|
is_time_ordered(now, tombstones[0].dateRemoved.getTime()),
|
|
"Tombstone timestamp should be recent"
|
|
);
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_locally_modified_remotely_deleted() {
|
|
let mergeTelemetryCounts;
|
|
let buf = await openMirror("locally_modified_remotely_deleted", {
|
|
recordStepTelemetry(name, took, counts) {
|
|
if (name == "merge") {
|
|
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
|
|
}
|
|
},
|
|
});
|
|
|
|
info("Set up mirror");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "bookmarkAAAA",
|
|
title: "A",
|
|
url: "http://example.com/a",
|
|
},
|
|
{
|
|
guid: "folderBBBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "B",
|
|
children: [
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
title: "C",
|
|
url: "http://example.com/c",
|
|
},
|
|
{
|
|
guid: "folderDDDDDD",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "D",
|
|
children: [
|
|
{
|
|
guid: "bookmarkEEEE",
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
[
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkAAAA", "folderBBBBBB"],
|
|
},
|
|
{
|
|
id: "bookmarkAAAA",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "A",
|
|
bmkUri: "http://example.com/a",
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "B",
|
|
children: ["bookmarkCCCC", "folderDDDDDD"],
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "C",
|
|
bmkUri: "http://example.com/c",
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "folderBBBBBB",
|
|
type: "folder",
|
|
title: "D",
|
|
children: ["bookmarkEEEE"],
|
|
},
|
|
{
|
|
id: "bookmarkEEEE",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "E",
|
|
bmkUri: "http://example.com/e",
|
|
},
|
|
],
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
info("Make local changes: change A; B > ((D > F) G)");
|
|
await PlacesUtils.bookmarks.update({
|
|
guid: "bookmarkAAAA",
|
|
title: "A (local)",
|
|
url: "http://example.com/a-local",
|
|
});
|
|
await PlacesUtils.bookmarks.insert({
|
|
guid: "bookmarkFFFF",
|
|
parentGuid: "folderDDDDDD",
|
|
title: "F (local)",
|
|
url: "http://example.com/f-local",
|
|
});
|
|
await PlacesUtils.bookmarks.insert({
|
|
guid: "bookmarkGGGG",
|
|
parentGuid: "folderBBBBBB",
|
|
title: "G (local)",
|
|
url: "http://example.com/g-local",
|
|
});
|
|
|
|
info("Make remote changes: delete A, B");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: [],
|
|
},
|
|
{
|
|
id: "bookmarkAAAA",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "bookmarkEEEE",
|
|
deleted: true,
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
["bookmarkAAAA", PlacesUtils.bookmarks.menuGuid],
|
|
"Should leave revived A and menu with new remote structure unmerged"
|
|
);
|
|
deepEqual(
|
|
mergeTelemetryCounts,
|
|
[
|
|
{ name: "items", count: 8 },
|
|
{ name: "localRevives", count: 1 },
|
|
{ name: "remoteDeletes", count: 2 },
|
|
],
|
|
"Should record telemetry for local item and remote folder deletions"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: ["bookmarkAAAA", "bookmarkFFFF", "bookmarkGGGG", "menu"],
|
|
deleted: [],
|
|
},
|
|
"Should upload A, relocated local orphans, and menu"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.menuGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "A (local)",
|
|
url: "http://example.com/a-local",
|
|
},
|
|
{
|
|
guid: "bookmarkFFFF",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 1,
|
|
title: "F (local)",
|
|
url: "http://example.com/f-local",
|
|
},
|
|
{
|
|
guid: "bookmarkGGGG",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 2,
|
|
title: "G (local)",
|
|
url: "http://example.com/g-local",
|
|
},
|
|
],
|
|
},
|
|
"Should restore A and relocate (F G) to menu"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(tombstones, [], "Should not store local tombstones");
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_locally_deleted_remotely_modified() {
|
|
let now = Date.now();
|
|
|
|
let mergeTelemetryCounts;
|
|
let buf = await openMirror("locally_deleted_remotely_modified", {
|
|
recordStepTelemetry(name, took, counts) {
|
|
if (name == "merge") {
|
|
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
|
|
}
|
|
},
|
|
});
|
|
|
|
info("Set up mirror");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "bookmarkAAAA",
|
|
title: "A",
|
|
url: "http://example.com/a",
|
|
},
|
|
{
|
|
guid: "folderBBBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "B",
|
|
children: [
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
title: "C",
|
|
url: "http://example.com/c",
|
|
},
|
|
{
|
|
guid: "folderDDDDDD",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "D",
|
|
children: [
|
|
{
|
|
guid: "bookmarkEEEE",
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
[
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkAAAA", "folderBBBBBB"],
|
|
},
|
|
{
|
|
id: "bookmarkAAAA",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "A",
|
|
bmkUri: "http://example.com/a",
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "B",
|
|
children: ["bookmarkCCCC", "folderDDDDDD"],
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "C",
|
|
bmkUri: "http://example.com/c",
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "folderBBBBBB",
|
|
type: "folder",
|
|
title: "D",
|
|
children: ["bookmarkEEEE"],
|
|
},
|
|
{
|
|
id: "bookmarkEEEE",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "E",
|
|
bmkUri: "http://example.com/e",
|
|
},
|
|
],
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
info("Make local changes: delete A, B");
|
|
await PlacesUtils.bookmarks.remove("bookmarkAAAA");
|
|
await PlacesUtils.bookmarks.remove("folderBBBBBB");
|
|
|
|
info("Make remote changes: change A; B > ((D > F) G)");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "bookmarkAAAA",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "A (remote)",
|
|
bmkUri: "http://example.com/a-remote",
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "B (remote)",
|
|
children: ["bookmarkCCCC", "folderDDDDDD", "bookmarkGGGG"],
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "folderBBBBBB",
|
|
type: "folder",
|
|
title: "D",
|
|
children: ["bookmarkEEEE", "bookmarkFFFF"],
|
|
},
|
|
{
|
|
id: "bookmarkFFFF",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "F (remote)",
|
|
bmkUri: "http://example.com/f-remote",
|
|
},
|
|
{
|
|
id: "bookmarkGGGG",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "G (remote)",
|
|
bmkUri: "http://example.com/g-remote",
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
["bookmarkFFFF", "bookmarkGGGG", "folderBBBBBB", "folderDDDDDD"],
|
|
"Should leave deleted B and D; relocated F and G unmerged"
|
|
);
|
|
deepEqual(
|
|
mergeTelemetryCounts,
|
|
[
|
|
{ name: "items", count: 8 },
|
|
{ name: "remoteRevives", count: 1 },
|
|
{ name: "localDeletes", count: 2 },
|
|
],
|
|
"Should record telemetry for remote item and local folder deletions"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: ["bookmarkFFFF", "bookmarkGGGG", "menu"],
|
|
deleted: ["bookmarkCCCC", "bookmarkEEEE", "folderBBBBBB", "folderDDDDDD"],
|
|
},
|
|
"Should upload relocated remote orphans and menu"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.menuGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "A (remote)",
|
|
url: "http://example.com/a-remote",
|
|
},
|
|
{
|
|
guid: "bookmarkFFFF",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 1,
|
|
title: "F (remote)",
|
|
url: "http://example.com/f-remote",
|
|
},
|
|
{
|
|
guid: "bookmarkGGGG",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 2,
|
|
title: "G (remote)",
|
|
url: "http://example.com/g-remote",
|
|
},
|
|
],
|
|
},
|
|
"Should restore A and relocate (F G) to menu"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(
|
|
tombstones.map(({ guid }) => guid),
|
|
["bookmarkCCCC", "bookmarkEEEE", "folderBBBBBB", "folderDDDDDD"],
|
|
"Should store local tombstones for deleted items; remove for undeleted"
|
|
);
|
|
Assert.ok(
|
|
tombstones.every(({ dateRemoved }) =>
|
|
is_time_ordered(now, dateRemoved.getTime())
|
|
),
|
|
"Local tombstone timestamps should be recent"
|
|
);
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_move_to_new_then_delete() {
|
|
let buf = await openMirror("move_to_new_then_delete");
|
|
|
|
info("Set up mirror: A > B > (C D)");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "folderAAAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "A",
|
|
children: [
|
|
{
|
|
guid: "folderBBBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "B",
|
|
children: [
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
url: "http://example.com/c",
|
|
title: "C",
|
|
},
|
|
{
|
|
guid: "bookmarkDDDD",
|
|
url: "http://example.com/d",
|
|
title: "D",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
shuffle([
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderAAAAAA"],
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "A",
|
|
children: ["folderBBBBBB"],
|
|
},
|
|
{
|
|
id: "folderBBBBBB",
|
|
parentid: "folderAAAAAA",
|
|
type: "folder",
|
|
title: "B",
|
|
children: ["bookmarkCCCC", "bookmarkDDDD"],
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "C",
|
|
bmkUri: "http://example.com/c",
|
|
},
|
|
{
|
|
id: "bookmarkDDDD",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "D",
|
|
bmkUri: "http://example.com/d",
|
|
},
|
|
]),
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
info("Make local changes: E > A, delete E");
|
|
await PlacesUtils.bookmarks.insert({
|
|
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
|
guid: "folderEEEEEE",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "E",
|
|
});
|
|
await PlacesUtils.bookmarks.update({
|
|
guid: "folderAAAAAA",
|
|
parentGuid: "folderEEEEEE",
|
|
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
|
|
});
|
|
// E isn't synced, so we shouldn't upload a tombstone.
|
|
await PlacesUtils.bookmarks.remove("folderEEEEEE");
|
|
|
|
info("Make remote changes");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "folderBBBBBB",
|
|
type: "bookmark",
|
|
title: "C (remote)",
|
|
bmkUri: "http://example.com/c-remote",
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
["bookmarkCCCC", PlacesUtils.bookmarks.toolbarGuid],
|
|
"Should leave revived C and toolbar with new remote structure unmerged"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: ["bookmarkCCCC", "menu", "toolbar"],
|
|
deleted: ["bookmarkDDDD", "folderAAAAAA", "folderBBBBBB"],
|
|
},
|
|
"Should upload records for Menu > C, Toolbar"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.rootGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.rootGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "",
|
|
children: [
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "C (remote)",
|
|
url: "http://example.com/c-remote",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.toolbarGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 1,
|
|
title: BookmarksToolbarTitle,
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.unfiledGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 3,
|
|
title: UnfiledBookmarksTitle,
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.mobileGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 4,
|
|
title: MobileBookmarksTitle,
|
|
},
|
|
],
|
|
},
|
|
"Should move C to closest surviving parent"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(
|
|
tombstones.map(({ guid }) => guid),
|
|
["bookmarkDDDD", "folderAAAAAA", "folderBBBBBB"],
|
|
"Should store local tombstones for (D A B)"
|
|
);
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_nonexistent_on_one_side() {
|
|
let buf = await openMirror("nonexistent_on_one_side");
|
|
|
|
info("Set up empty mirror");
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
// A doesn't exist in the mirror.
|
|
info("Create local tombstone for nonexistent remote item A");
|
|
await PlacesUtils.bookmarks.insert({
|
|
guid: "bookmarkAAAA",
|
|
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
|
title: "A",
|
|
url: "http://example.com/a",
|
|
// Pretend a bookmark restore added A, so that we'll write a tombstone when
|
|
// we remove it.
|
|
source: PlacesUtils.bookmarks.SOURCES.RESTORE,
|
|
});
|
|
await PlacesUtils.bookmarks.remove("bookmarkAAAA");
|
|
|
|
// B doesn't exist in Places, and we don't currently persist tombstones (bug
|
|
// 1343103), so we should ignore it.
|
|
info("Create remote tombstone for nonexistent local item B");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "bookmarkBBBB",
|
|
deleted: true,
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
[PlacesUtils.bookmarks.menuGuid],
|
|
"Should leave menu with new remote structure unmerged"
|
|
);
|
|
|
|
let menuInfo = await PlacesUtils.bookmarks.fetch(
|
|
PlacesUtils.bookmarks.menuGuid
|
|
);
|
|
|
|
// We should still upload a record for the menu, since we changed its
|
|
// children when we added then removed A.
|
|
deepEqual(changesToUpload, {
|
|
menu: {
|
|
tombstone: false,
|
|
counter: 2,
|
|
synced: false,
|
|
cleartext: {
|
|
id: "menu",
|
|
type: "folder",
|
|
parentid: "places",
|
|
hasDupe: true,
|
|
parentName: "",
|
|
dateAdded: menuInfo.dateAdded.getTime(),
|
|
title: BookmarksMenuTitle,
|
|
children: [],
|
|
},
|
|
},
|
|
});
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_clear_folder_then_delete() {
|
|
let buf = await openMirror("clear_folder_then_delete");
|
|
|
|
info("Set up mirror");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "folderAAAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "A",
|
|
children: [
|
|
{
|
|
guid: "bookmarkBBBB",
|
|
url: "http://example.com/b",
|
|
title: "B",
|
|
},
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
url: "http://example.com/c",
|
|
title: "C",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: "folderDDDDDD",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "D",
|
|
children: [
|
|
{
|
|
guid: "bookmarkEEEE",
|
|
url: "http://example.com/e",
|
|
title: "E",
|
|
},
|
|
{
|
|
guid: "bookmarkFFFF",
|
|
url: "http://example.com/f",
|
|
title: "F",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
[
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderAAAAAA", "folderDDDDDD"],
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "A",
|
|
children: ["bookmarkBBBB", "bookmarkCCCC"],
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
parentid: "folderAAAAAA",
|
|
type: "bookmark",
|
|
title: "B",
|
|
bmkUri: "http://example.com/b",
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "folderAAAAAA",
|
|
type: "bookmark",
|
|
title: "C",
|
|
bmkUri: "http://example.com/c",
|
|
},
|
|
{
|
|
id: "folderDDDDDD",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "D",
|
|
children: ["bookmarkEEEE", "bookmarkFFFF"],
|
|
},
|
|
{
|
|
id: "bookmarkEEEE",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "E",
|
|
bmkUri: "http://example.com/e",
|
|
},
|
|
{
|
|
id: "bookmarkFFFF",
|
|
parentid: "folderDDDDDD",
|
|
type: "bookmark",
|
|
title: "F",
|
|
bmkUri: "http://example.com/f",
|
|
},
|
|
],
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
info("Make local changes: Menu > E, Mobile > F, delete D");
|
|
await PlacesUtils.bookmarks.update({
|
|
guid: "bookmarkEEEE",
|
|
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
|
index: 0,
|
|
});
|
|
await PlacesUtils.bookmarks.update({
|
|
guid: "bookmarkFFFF",
|
|
parentGuid: PlacesUtils.bookmarks.mobileGuid,
|
|
index: 0,
|
|
});
|
|
await PlacesUtils.bookmarks.remove("folderDDDDDD");
|
|
|
|
info("Make remote changes: Menu > B, Unfiled > C, delete A");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkBBBB", "folderDDDDDD"],
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "B",
|
|
bmkUri: "http://example.com/b",
|
|
},
|
|
{
|
|
id: "unfiled",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkCCCC"],
|
|
},
|
|
{
|
|
id: "bookmarkCCCC",
|
|
parentid: "unfiled",
|
|
type: "bookmark",
|
|
title: "C",
|
|
bmkUri: "http://example.com/c",
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
deleted: true,
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
[PlacesUtils.bookmarks.menuGuid, PlacesUtils.bookmarks.mobileGuid],
|
|
"Should leave menu and mobile with new remote structure unmerged"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: ["bookmarkEEEE", "bookmarkFFFF", "menu", "mobile"],
|
|
deleted: ["folderDDDDDD"],
|
|
},
|
|
"Should upload locally moved and deleted items"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.rootGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.rootGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "",
|
|
children: [
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "B",
|
|
url: "http://example.com/b",
|
|
},
|
|
{
|
|
guid: "bookmarkEEEE",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 1,
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.toolbarGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 1,
|
|
title: BookmarksToolbarTitle,
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.unfiledGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 3,
|
|
title: UnfiledBookmarksTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkCCCC",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "C",
|
|
url: "http://example.com/c",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.mobileGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 4,
|
|
title: MobileBookmarksTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkFFFF",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "F",
|
|
url: "http://example.com/f",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
"Should not orphan moved children of a deleted folder"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(
|
|
tombstones.map(({ guid }) => guid),
|
|
["folderDDDDDD"],
|
|
"Should store local tombstone for D"
|
|
);
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_newer_move_to_deleted() {
|
|
let buf = await openMirror("test_newer_move_to_deleted");
|
|
|
|
info("Set up mirror");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "folderAAAAAA",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "A",
|
|
children: [
|
|
{
|
|
guid: "bookmarkBBBB",
|
|
url: "http://example.com/b",
|
|
title: "B",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: "folderCCCCCC",
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
title: "C",
|
|
children: [
|
|
{
|
|
guid: "bookmarkDDDD",
|
|
url: "http://example.com/d",
|
|
title: "D",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
[
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderAAAAAA", "folderCCCCCC"],
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "A",
|
|
children: ["bookmarkBBBB"],
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
parentid: "folderAAAAAA",
|
|
type: "bookmark",
|
|
title: "B",
|
|
bmkUri: "http://example.com/b",
|
|
},
|
|
{
|
|
id: "folderCCCCCC",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "C",
|
|
children: ["bookmarkDDDD"],
|
|
},
|
|
{
|
|
id: "bookmarkDDDD",
|
|
parentid: "folderCCCCCC",
|
|
type: "bookmark",
|
|
title: "D",
|
|
bmkUri: "http://example.com/d",
|
|
},
|
|
],
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
let now = Date.now();
|
|
|
|
// A will have a newer local timestamp. However, we should *not* revert
|
|
// remotely moving B to the toolbar. (Locally, B exists in A, but we
|
|
// deleted the now-empty A remotely).
|
|
info("Make local changes: A > E, Toolbar > D, delete C");
|
|
await PlacesUtils.bookmarks.insert({
|
|
guid: "bookmarkEEEE",
|
|
parentGuid: "folderAAAAAA",
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
dateAdded: new Date(now),
|
|
lastModified: new Date(now),
|
|
});
|
|
await PlacesUtils.bookmarks.update({
|
|
guid: "bookmarkDDDD",
|
|
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
|
index: 0,
|
|
lastModified: new Date(now),
|
|
});
|
|
await PlacesUtils.bookmarks.remove("folderCCCCCC");
|
|
|
|
// C will have a newer remote timestamp. However, we should *not* revert
|
|
// locally moving D to the toolbar. (Locally, D exists in C, but we
|
|
// deleted the now-empty C locally).
|
|
info("Make remote changes: C > F, Toolbar > B, delete A");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["folderCCCCCC"],
|
|
},
|
|
{
|
|
id: "folderCCCCCC",
|
|
parentid: "menu",
|
|
type: "folder",
|
|
title: "C",
|
|
children: ["bookmarkDDDD", "bookmarkFFFF"],
|
|
modified: now / 1000 + 5,
|
|
},
|
|
{
|
|
id: "bookmarkFFFF",
|
|
parentid: "folderCCCCCC",
|
|
type: "bookmark",
|
|
title: "F",
|
|
bmkUri: "http://example.com/f",
|
|
},
|
|
{
|
|
id: "toolbar",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkBBBB"],
|
|
modified: now / 1000 - 5,
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
parentid: "toolbar",
|
|
type: "bookmark",
|
|
title: "B",
|
|
bmkUri: "http://example.com/b",
|
|
modified: now / 1000 - 5,
|
|
},
|
|
{
|
|
id: "folderAAAAAA",
|
|
deleted: true,
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply({
|
|
localTimeSeconds: now / 1000,
|
|
remoteTimeSeconds: now / 1000,
|
|
});
|
|
deepEqual(
|
|
await buf.fetchUnmergedGuids(),
|
|
[
|
|
"bookmarkFFFF",
|
|
"folderCCCCCC",
|
|
PlacesUtils.bookmarks.menuGuid,
|
|
PlacesUtils.bookmarks.toolbarGuid,
|
|
],
|
|
"Should leave deleted C; revived F and roots with new remote structure unmerged"
|
|
);
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: [
|
|
"bookmarkDDDD",
|
|
"bookmarkEEEE",
|
|
"bookmarkFFFF",
|
|
"menu",
|
|
"toolbar",
|
|
],
|
|
deleted: ["folderCCCCCC"],
|
|
},
|
|
"Should upload new and moved items"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.rootGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.rootGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: "",
|
|
children: [
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkEEEE",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "E",
|
|
url: "http://example.com/e",
|
|
},
|
|
{
|
|
guid: "bookmarkFFFF",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 1,
|
|
title: "F",
|
|
url: "http://example.com/f",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.toolbarGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 1,
|
|
title: BookmarksToolbarTitle,
|
|
children: [
|
|
{
|
|
guid: "bookmarkDDDD",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 0,
|
|
title: "D",
|
|
url: "http://example.com/d",
|
|
},
|
|
{
|
|
guid: "bookmarkBBBB",
|
|
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
|
index: 1,
|
|
title: "B",
|
|
url: "http://example.com/b",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.unfiledGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 3,
|
|
title: UnfiledBookmarksTitle,
|
|
},
|
|
{
|
|
guid: PlacesUtils.bookmarks.mobileGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 4,
|
|
title: MobileBookmarksTitle,
|
|
},
|
|
],
|
|
},
|
|
"Should not decide to keep newly moved items in deleted parents"
|
|
);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(
|
|
tombstones.map(({ guid }) => guid),
|
|
["folderCCCCCC"],
|
|
"Should store local tombstone for C"
|
|
);
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|
|
|
|
add_task(async function test_remotely_deleted_also_removes_keyword() {
|
|
let buf = await openMirror("remotely_deleted_removes_keyword");
|
|
|
|
info("Set up mirror");
|
|
await PlacesUtils.bookmarks.insertTree({
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
children: [
|
|
{
|
|
guid: "bookmarkAAAA",
|
|
title: "A",
|
|
url: "http://example.com/a",
|
|
keyword: "keyworda",
|
|
},
|
|
{
|
|
guid: "bookmarkBBBB",
|
|
title: "B",
|
|
url: "http://example.com/b",
|
|
keyword: "keywordb",
|
|
},
|
|
],
|
|
});
|
|
await storeRecords(
|
|
buf,
|
|
[
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: ["bookmarkAAAA", "bookmarkBBBB"],
|
|
},
|
|
{
|
|
id: "bookmarkAAAA",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "A",
|
|
bmkUri: "http://example.com/a",
|
|
keyword: "keyworda",
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
parentid: "menu",
|
|
type: "bookmark",
|
|
title: "B",
|
|
bmkUri: "http://example.com/b",
|
|
keyword: "keywordb",
|
|
},
|
|
],
|
|
{ needsMerge: false }
|
|
);
|
|
await PlacesTestUtils.markBookmarksAsSynced();
|
|
|
|
// Validate the keywords exists
|
|
let has_keyword_a = await PlacesUtils.keywords.fetch({
|
|
url: "http://example.com/a",
|
|
});
|
|
Assert.equal(has_keyword_a.keyword, "keyworda");
|
|
|
|
let has_keyword_b = await PlacesUtils.keywords.fetch({
|
|
url: "http://example.com/b",
|
|
});
|
|
Assert.equal(has_keyword_b.keyword, "keywordb");
|
|
|
|
info("Make remote changes: delete A & B");
|
|
await storeRecords(buf, [
|
|
{
|
|
id: "menu",
|
|
parentid: "places",
|
|
type: "folder",
|
|
children: [],
|
|
},
|
|
{
|
|
id: "bookmarkAAAA",
|
|
deleted: true,
|
|
},
|
|
{
|
|
id: "bookmarkBBBB",
|
|
deleted: true,
|
|
},
|
|
]);
|
|
|
|
info("Apply remote");
|
|
let changesToUpload = await buf.apply();
|
|
|
|
let idsToUpload = inspectChangeRecords(changesToUpload);
|
|
deepEqual(
|
|
idsToUpload,
|
|
{
|
|
updated: [],
|
|
deleted: [],
|
|
},
|
|
"No local changes done"
|
|
);
|
|
|
|
await assertLocalTree(
|
|
PlacesUtils.bookmarks.menuGuid,
|
|
{
|
|
guid: PlacesUtils.bookmarks.menuGuid,
|
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
index: 0,
|
|
title: BookmarksMenuTitle,
|
|
},
|
|
"Should've remove A & B from menu"
|
|
);
|
|
|
|
// Validate the keyword no longer exists after removing the bookmark
|
|
let no_keyword_a = await PlacesUtils.keywords.fetch({
|
|
url: "http://example.com/a",
|
|
});
|
|
Assert.equal(no_keyword_a, null);
|
|
|
|
// Both keywords should've been removed after the sync
|
|
let no_keyword_b = await PlacesUtils.keywords.fetch({
|
|
url: "http://example.com/b",
|
|
});
|
|
Assert.equal(no_keyword_b, null);
|
|
|
|
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
|
|
deepEqual(tombstones, [], "Should not store local tombstones");
|
|
|
|
await storeChangesInMirror(buf, changesToUpload);
|
|
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
|
|
|
|
await buf.finalize();
|
|
await PlacesUtils.bookmarks.eraseEverything();
|
|
await PlacesSyncUtils.bookmarks.reset();
|
|
});
|