/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ add_task(async function test_duping_local_newer() { let mergeTelemetryCounts; let buf = await openMirror("duping_local_newer", { recordStepTelemetry(name, took, counts) { if (name == "merge") { mergeTelemetryCounts = counts.filter(({ count }) => count > 0); } }, }); let localModified = new Date(); info("Start with empty local and mirror with merged items"); await storeRecords( buf, [ { id: "menu", parentid: "places", type: "folder", children: ["bookmarkAAA5"], dateAdded: localModified.getTime(), }, { id: "bookmarkAAA5", parentid: "menu", type: "bookmark", bmkUri: "http://example.com/a", title: "A", dateAdded: localModified.getTime(), }, ], { needsMerge: false } ); await PlacesTestUtils.markBookmarksAsSynced(); info("Add newer local dupes"); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.menuGuid, children: [ { guid: "bookmarkAAA1", title: "A", url: "http://example.com/a", dateAdded: localModified, lastModified: localModified, }, { guid: "bookmarkAAA2", title: "A", url: "http://example.com/a", dateAdded: localModified, lastModified: localModified, }, { guid: "bookmarkAAA3", title: "A", url: "http://example.com/a", dateAdded: localModified, lastModified: localModified, }, ], }); info("Add older remote dupes"); await storeRecords(buf, [ { id: "menu", parentid: "places", type: "folder", children: ["bookmarkAAAA", "bookmarkAAA4", "bookmarkAAA5"], modified: localModified / 1000 - 5, }, { id: "bookmarkAAAA", parentid: "menu", type: "bookmark", bmkUri: "http://example.com/a", title: "A", keyword: "kw", tags: ["remote", "tags"], modified: localModified / 1000 - 5, }, { id: "bookmarkAAA4", parentid: "menu", type: "bookmark", bmkUri: "http://example.com/a", title: "A", modified: localModified / 1000 - 5, }, ]); info("Apply remote"); let changesToUpload = await buf.apply({ remoteTimeSeconds: localModified / 1000, }); deepEqual( await buf.fetchUnmergedGuids(), ["bookmarkAAA4", "bookmarkAAAA", PlacesUtils.bookmarks.menuGuid], "Should leave A4, A, menu with new remote structure unmerged" ); deepEqual( mergeTelemetryCounts, [ { name: "items", count: 9 }, { name: "dupes", count: 2 }, ], "Should record telemetry with dupe counts" ); let menuInfo = await PlacesUtils.bookmarks.fetch( PlacesUtils.bookmarks.menuGuid ); deepEqual( changesToUpload, { menu: { tombstone: false, counter: 1, synced: false, cleartext: { id: "menu", type: "folder", parentid: "places", hasDupe: true, parentName: "", dateAdded: menuInfo.dateAdded.getTime(), title: menuInfo.title, children: [ "bookmarkAAAA", "bookmarkAAA4", "bookmarkAAA3", "bookmarkAAA5", ], }, }, // Note that we always reupload the deduped local item, because content // matching doesn't account for attributes like keywords, synced annos, or // tags. bookmarkAAAA: { tombstone: false, counter: 1, synced: false, cleartext: { id: "bookmarkAAAA", type: "bookmark", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: localModified.getTime(), title: "A", bmkUri: "http://example.com/a", }, }, // Unchanged from local. bookmarkAAA4: { tombstone: false, counter: 1, synced: false, cleartext: { id: "bookmarkAAA4", type: "bookmark", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: localModified.getTime(), title: "A", bmkUri: "http://example.com/a", }, }, bookmarkAAA3: { tombstone: false, counter: 1, synced: false, cleartext: { id: "bookmarkAAA3", type: "bookmark", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: localModified.getTime(), title: "A", bmkUri: "http://example.com/a", }, }, bookmarkAAA5: { tombstone: false, counter: 1, synced: false, cleartext: { id: "bookmarkAAA5", type: "bookmark", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: localModified.getTime(), title: "A", bmkUri: "http://example.com/a", }, }, }, "Should uploaded newer deduped local items" ); 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", url: "http://example.com/a", }, { guid: "bookmarkAAA4", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 1, title: "A", url: "http://example.com/a", }, { guid: "bookmarkAAA3", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 2, title: "A", url: "http://example.com/a", }, { guid: "bookmarkAAA5", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 3, title: "A", url: "http://example.com/a", }, ], }, "Should dedupe local multiple bookmarks with similar contents" ); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); await PlacesSyncUtils.bookmarks.reset(); }); add_task(async function test_duping_remote_newer() { let buf = await openMirror("duping_remote_new"); let localModified = new Date(); info("Set up mirror"); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.menuGuid, children: [ { // Shouldn't dupe to `folderA11111` because its sync status is "NORMAL". guid: "folderAAAAAA", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "A", children: [ { // Shouldn't dupe to `bookmarkG111`. guid: "bookmarkGGGG", url: "http://example.com/g", title: "G", }, ], }, ], }); await storeRecords( buf, shuffle([ { id: "menu", parentid: "places", type: "folder", children: ["folderAAAAAA"], }, { id: "folderAAAAAA", parentid: "menu", type: "folder", title: "A", children: ["bookmarkGGGG"], }, { id: "bookmarkGGGG", parentid: "folderAAAAAA", type: "bookmark", title: "G", bmkUri: "http://example.com/g", }, ]), { needsMerge: false } ); await PlacesTestUtils.markBookmarksAsSynced(); info("Insert local dupes"); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.menuGuid, children: [ { // Should dupe to `folderB11111`. guid: "folderBBBBBB", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "B", dateAdded: localModified, lastModified: localModified, children: [ { // Should dupe to `bookmarkC222`. guid: "bookmarkC111", url: "http://example.com/c", title: "C", dateAdded: localModified, lastModified: localModified, }, { // Should dupe to `separatorF11` because the positions are the same. guid: "separatorFFF", type: PlacesUtils.bookmarks.TYPE_SEPARATOR, dateAdded: localModified, lastModified: localModified, }, ], }, { // Shouldn't dupe to `separatorE11`, because the positions are different. guid: "separatorEEE", type: PlacesUtils.bookmarks.TYPE_SEPARATOR, dateAdded: localModified, lastModified: localModified, }, { // Shouldn't dupe to `bookmarkC222` because the parents are different. guid: "bookmarkCCCC", url: "http://example.com/c", title: "C", dateAdded: localModified, lastModified: localModified, }, { // Should dupe to `queryD111111`. guid: "queryDDDDDDD", url: "place:maxResults=10&sort=8", title: "Most Visited", dateAdded: localModified, lastModified: localModified, }, ], }); // Make sure we still dedupe this even though it doesn't have SYNC_STATUS.NEW PlacesTestUtils.setBookmarkSyncFields({ guid: "folderBBBBBB", syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN, }); // Not a candidate for `bookmarkH111` because we didn't dupe `folderAAAAAA`. await PlacesUtils.bookmarks.insert({ parentGuid: "folderAAAAAA", guid: "bookmarkHHHH", url: "http://example.com/h", title: "H", dateAdded: localModified, lastModified: localModified, }); info("Make remote changes"); await storeRecords( buf, shuffle([ { id: "menu", parentid: "places", type: "folder", children: [ "folderAAAAAA", "folderB11111", "folderA11111", "separatorE11", "queryD111111", ], dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "folderB11111", parentid: "menu", type: "folder", title: "B", children: ["bookmarkC222", "separatorF11"], dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "bookmarkC222", parentid: "folderB11111", type: "bookmark", bmkUri: "http://example.com/c", title: "C", dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "separatorF11", parentid: "folderB11111", type: "separator", dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "folderA11111", parentid: "menu", type: "folder", title: "A", children: ["bookmarkG111"], dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "bookmarkG111", parentid: "folderA11111", type: "bookmark", bmkUri: "http://example.com/g", title: "G", dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "separatorE11", parentid: "menu", type: "separator", dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, { id: "queryD111111", parentid: "menu", type: "query", bmkUri: "place:maxResults=10&sort=8", title: "Most Visited", dateAdded: localModified.getTime(), modified: localModified / 1000 + 5, }, ]) ); info("Apply remote"); let changesToUpload = await buf.apply({ remoteTimeSeconds: localModified / 1000, }); deepEqual( await buf.fetchUnmergedGuids(), [PlacesUtils.bookmarks.menuGuid], "Should leave menu with new remote structure unmerged" ); let idsToUpload = inspectChangeRecords(changesToUpload); deepEqual( idsToUpload, { updated: [ "bookmarkCCCC", "bookmarkHHHH", "folderAAAAAA", "menu", "separatorEEE", ], deleted: [], }, "Should not upload deduped local records" ); 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: "folderAAAAAA", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: "A", children: [ { guid: "bookmarkGGGG", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "G", url: "http://example.com/g", }, { guid: "bookmarkHHHH", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 1, title: "H", url: "http://example.com/h", }, ], }, { guid: "folderB11111", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 1, title: "B", children: [ { guid: "bookmarkC222", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "C", url: "http://example.com/c", }, { guid: "separatorF11", type: PlacesUtils.bookmarks.TYPE_SEPARATOR, index: 1, title: "", }, ], }, { guid: "folderA11111", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 2, title: "A", children: [ { guid: "bookmarkG111", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "G", url: "http://example.com/g", }, ], }, { guid: "separatorE11", type: PlacesUtils.bookmarks.TYPE_SEPARATOR, index: 3, title: "", }, { guid: "queryD111111", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 4, title: "Most Visited", url: "place:maxResults=10&sort=8", }, { guid: "separatorEEE", type: PlacesUtils.bookmarks.TYPE_SEPARATOR, index: 5, title: "", }, { guid: "bookmarkCCCC", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 6, title: "C", url: "http://example.com/c", }, ], }, { 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 dedupe matching NEW bookmarks" ); ok( ( await PlacesTestUtils.fetchBookmarkSyncFields( "menu________", "folderB11111", "bookmarkC222", "separatorF11", "folderA11111", "bookmarkG111", "separatorE11", "queryD111111" ) ).every(info => info.syncStatus == PlacesUtils.bookmarks.SYNC_STATUS.NORMAL) ); 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_duping_both() { let buf = await openMirror("duping_both"); let now = Date.now(); info("Start with empty mirror"); await PlacesTestUtils.markBookmarksAsSynced(); info("Add local dupes"); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.menuGuid, children: [ { // `folderAAAAA1` is older than `folderAAAAAA`, but we should still flag // it for upload because it has a new structure (`bookmarkCCCC`). guid: "folderAAAAA1", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "A", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), children: [ { // Shouldn't upload, since `bookmarkBBBB` is newer. guid: "bookmarkBBB1", title: "B", url: "http://example.com/b", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), }, { // Should upload, since `bookmarkCCCC` doesn't exist on the server and // has no content matches. guid: "bookmarkCCCC", title: "C", url: "http://example.com/c", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), }, ], }, { // `folderDDDDD1` should keep complete local structure, but we'll still // flag it for reupload because it's newer than `folderDDDDDD`. guid: "folderDDDDD1", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "D", dateAdded: new Date(now - 10000), lastModified: new Date(now + 5000), children: [ { guid: "bookmarkEEE1", title: "E", url: "http://example.com/e", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), }, ], }, { // `folderFFFFF1` should keep complete remote value and structure, so // we shouldn't upload it or its children. guid: "folderFFFFF1", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "F", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), children: [ { guid: "bookmarkGGG1", title: "G", url: "http://example.com/g", dateAdded: new Date(now - 10000), lastModified: new Date(now - 5000), }, ], }, ], }); info("Add remote dupes"); await storeRecords(buf, [ { id: "menu", parentid: "places", type: "folder", children: ["folderAAAAAA", "folderDDDDDD", "folderFFFFFF"], }, { id: "folderAAAAAA", parentid: "menu", type: "folder", title: "A", dateAdded: now - 10000, modified: now / 1000 + 5, children: ["bookmarkBBBB"], }, { id: "bookmarkBBBB", parentid: "folderAAAAAA", type: "bookmark", bmkUri: "http://example.com/b", title: "B", dateAdded: now - 10000, modified: now / 1000 + 5, }, { id: "folderDDDDDD", parentid: "menu", type: "folder", title: "D", dateAdded: now - 10000, modified: now / 1000 - 5, children: ["bookmarkEEEE"], }, { id: "bookmarkEEEE", parentid: "folderDDDDDD", type: "bookmark", bmkUri: "http://example.com/e", title: "E", dateAdded: now - 10000, modified: now / 1000 + 5, }, { id: "folderFFFFFF", parentid: "menu", type: "folder", title: "F", dateAdded: now - 10000, modified: now / 1000 + 5, children: ["bookmarkGGGG", "bookmarkHHHH"], }, { id: "bookmarkGGGG", parentid: "folderFFFFFF", type: "bookmark", bmkUri: "http://example.com/g", title: "G", dateAdded: now - 10000, modified: now / 1000 - 5, }, { id: "bookmarkHHHH", parentid: "folderFFFFFF", type: "bookmark", bmkUri: "http://example.com/h", title: "H", dateAdded: now - 10000, modified: now / 1000 + 5, }, ]); info("Apply remote"); let changesToUpload = await buf.apply({ remoteTimeSeconds: now / 1000, }); let menuInfo = await PlacesUtils.bookmarks.fetch( PlacesUtils.bookmarks.menuGuid ); deepEqual( changesToUpload, { menu: { tombstone: false, counter: 1, synced: false, cleartext: { id: "menu", type: "folder", parentid: "places", hasDupe: true, parentName: "", dateAdded: menuInfo.dateAdded.getTime(), title: menuInfo.title, children: ["folderAAAAAA", "folderDDDDDD", "folderFFFFFF"], }, }, folderAAAAAA: { tombstone: false, counter: 1, synced: false, cleartext: { id: "folderAAAAAA", type: "folder", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: now - 10000, title: "A", children: ["bookmarkBBBB", "bookmarkCCCC"], }, }, bookmarkCCCC: { tombstone: false, counter: 1, synced: false, cleartext: { id: "bookmarkCCCC", type: "bookmark", parentid: "folderAAAAAA", hasDupe: true, parentName: "A", dateAdded: now - 10000, title: "C", bmkUri: "http://example.com/c", }, }, folderDDDDDD: { tombstone: false, counter: 1, synced: false, cleartext: { id: "folderDDDDDD", type: "folder", parentid: "menu", hasDupe: true, parentName: menuInfo.title, dateAdded: now - 10000, title: "D", children: ["bookmarkEEEE"], }, }, }, "Should upload new and newer locally deduped items" ); await assertLocalTree( PlacesUtils.bookmarks.menuGuid, { guid: PlacesUtils.bookmarks.menuGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: BookmarksMenuTitle, children: [ { guid: "folderAAAAAA", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: "A", children: [ { guid: "bookmarkBBBB", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "B", url: "http://example.com/b", }, { guid: "bookmarkCCCC", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 1, title: "C", url: "http://example.com/c", }, ], }, { guid: "folderDDDDDD", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 1, title: "D", children: [ { guid: "bookmarkEEEE", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "E", url: "http://example.com/e", }, ], }, { guid: "folderFFFFFF", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 2, title: "F", children: [ { guid: "bookmarkGGGG", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "G", url: "http://example.com/g", }, { guid: "bookmarkHHHH", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 1, title: "H", url: "http://example.com/h", }, ], }, ], }, "Should change local GUIDs for mixed older and newer items" ); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); await PlacesSyncUtils.bookmarks.reset(); }); add_task(async function test_applying_two_empty_folders_doesnt_smush() { let buf = await openMirror("applying_two_empty_folders_doesnt_smush"); info("Set up empty mirror"); await PlacesTestUtils.markBookmarksAsSynced(); info("Make remote changes"); await storeRecords( buf, shuffle([ { id: "mobile", parentid: "places", type: "folder", children: ["emptyempty01", "emptyempty02"], }, { id: "emptyempty01", parentid: "mobile", type: "folder", title: "Empty", }, { id: "emptyempty02", parentid: "mobile", type: "folder", title: "Empty", }, ]) ); info("Apply remote"); let changesToUpload = await buf.apply(); deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items"); let idsToUpload = inspectChangeRecords(changesToUpload); deepEqual( idsToUpload, { updated: [], deleted: [], }, "Should not upload records for remote-only value changes" ); await assertLocalTree( PlacesUtils.bookmarks.mobileGuid, { guid: PlacesUtils.bookmarks.mobileGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 4, title: "mobile", children: [ { guid: "emptyempty01", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: "Empty", }, { guid: "emptyempty02", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 1, title: "Empty", }, ], }, "Should not smush 1 and 2" ); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); await PlacesSyncUtils.bookmarks.reset(); }); add_task(async function test_applying_two_empty_folders_matches_only_one() { let buf = await openMirror("applying_two_empty_folders_doesnt_smush"); info("Set up empty mirror"); await PlacesTestUtils.markBookmarksAsSynced(); info("Make local changes"); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.mobileGuid, children: [ { guid: "emptyempty02", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "Empty", }, { guid: "emptyemptyL0", type: PlacesUtils.bookmarks.TYPE_FOLDER, title: "Empty", }, ], }); info("Make remote changes"); await storeRecords( buf, shuffle([ { id: "mobile", parentid: "places", type: "folder", children: ["emptyempty01", "emptyempty02", "emptyempty03"], }, { id: "emptyempty01", parentid: "mobile", type: "folder", title: "Empty", }, { id: "emptyempty02", parentid: "mobile", type: "folder", title: "Empty", }, { id: "emptyempty03", parentid: "mobile", type: "folder", title: "Empty", }, ]) ); info("Apply remote"); let changesToUpload = await buf.apply(); deepEqual( await buf.fetchUnmergedGuids(), [PlacesUtils.bookmarks.mobileGuid], "Should leave mobile with new remote structure unmerged" ); let idsToUpload = inspectChangeRecords(changesToUpload); deepEqual( idsToUpload, { updated: ["mobile"], deleted: [], }, "Should not upload records after applying empty folders" ); await assertLocalTree( PlacesUtils.bookmarks.mobileGuid, { guid: PlacesUtils.bookmarks.mobileGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 4, title: "mobile", children: [ { guid: "emptyempty01", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: "Empty", }, { guid: "emptyempty02", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 1, title: "Empty", }, { guid: "emptyempty03", type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 2, title: "Empty", }, ], }, "Should apply 1 and dedupe L0 to 3" ); await storeChangesInMirror(buf, changesToUpload); deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items"); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); await PlacesSyncUtils.bookmarks.reset(); }); // Bug 747699. add_task(async function test_duping_mobile_bookmarks() { let buf = await openMirror("duping_mobile_bookmarks"); info("Set up empty mirror with localized mobile root title"); let mobileInfo = await PlacesUtils.bookmarks.fetch( PlacesUtils.bookmarks.mobileGuid ); await PlacesUtils.bookmarks.update({ guid: PlacesUtils.bookmarks.mobileGuid, title: "Favoritos do celular", }); await PlacesTestUtils.markBookmarksAsSynced(); info("Make local changes"); await PlacesUtils.bookmarks.insert({ guid: "bookmarkAAA1", parentGuid: PlacesUtils.bookmarks.mobileGuid, title: "A", url: "http://example.com/a", }); info("Make remote changes"); await storeRecords( buf, shuffle([ { id: "mobile", parentid: "places", type: "folder", children: ["bookmarkAAAA"], }, { id: "bookmarkAAAA", parentid: "mobile", type: "bookmark", title: "A", bmkUri: "http://example.com/a", }, ]) ); info("Apply remote"); let changesToUpload = await buf.apply(); deepEqual( await buf.fetchUnmergedGuids(), [PlacesUtils.bookmarks.mobileGuid], "Should leave mobile with new remote structure unmerged" ); let idsToUpload = inspectChangeRecords(changesToUpload); deepEqual( idsToUpload, { updated: ["mobile"], deleted: [], }, "Should not upload records after applying deduped mobile bookmark" ); await assertLocalTree( PlacesUtils.bookmarks.mobileGuid, { guid: PlacesUtils.bookmarks.mobileGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 4, title: "Favoritos do celular", children: [ { guid: "bookmarkAAAA", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "A", url: "http://example.com/a", }, ], }, "Should dedupe A1 to A with different parent title" ); await storeChangesInMirror(buf, changesToUpload); deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items"); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); // Restore the original mobile root title. await PlacesUtils.bookmarks.update({ guid: PlacesUtils.bookmarks.mobileGuid, title: mobileInfo.title, }); await PlacesSyncUtils.bookmarks.reset(); }); add_task(async function test_duping_invalid() { // To check if invalid items are prevented from deduping info("Set up empty mirror"); await PlacesTestUtils.markBookmarksAsSynced(); await PlacesUtils.bookmarks.insertTree({ guid: PlacesUtils.bookmarks.menuGuid, children: [ { guid: "bookmarkAAA1", title: "A", url: "http://example.com/a", }, ], }); let buf = await openMirror("duping_invalid"); await storeRecords(buf, [ { id: "menu", parentid: "places", type: "folder", children: ["bookmarkAAA2"], }, { id: "bookmarkAAA2", parentid: "menu", type: "bookmark", title: "A", bmkUri: "http://example.com/a", }, ]); // Invalidate bookmarkAAA2 so that it does not dedupe to bookmarkAAA1 await buf.db.execute( `UPDATE items SET validity = :validity WHERE guid = :guid`, { validity: Ci.mozISyncedBookmarksMerger.VALIDITY_REPLACE, guid: "bookmarkAAA2", } ); let changesToUpload = await buf.apply(); deepEqual( changesToUpload.menu.cleartext.children, ["bookmarkAAA1"], "Should upload A1 in menu" ); ok( !changesToUpload.bookmarkAAA1.tombstone, "Should not upload tombstone for A1" ); ok(changesToUpload.bookmarkAAA2.tombstone, "Should upload tombstone for A2"); await assertLocalTree( PlacesUtils.bookmarks.menuGuid, { guid: PlacesUtils.bookmarks.menuGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: BookmarksMenuTitle, children: [ { guid: "bookmarkAAA1", type: PlacesUtils.bookmarks.TYPE_BOOKMARK, index: 0, title: "A", url: "http://example.com/a", }, ], }, "No deduping of invalid items" ); await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything(); await PlacesSyncUtils.bookmarks.reset(); });