Bug 1435556 - Write changed records back to the mirror and assert the remote tree in tests. draft
authorKit Cambridge <kit@yakshaving.ninja>
Tue, 06 Mar 2018 11:32:16 -0800
changeset 763861 971db612fc1adf2799e33319bb61e56cbf2f4f10
parent 763748 a4ef1082c51d5b4508882c22487f6c8de5b35e2a
child 763881 56f7ba62daf83349803221c56b1370a9b12ce44b
child 763885 d3e6d83418fad5de59708b389306b0081e94cc7a
push id101570
push userbmo:kit@mozilla.com
push dateTue, 06 Mar 2018 19:34:00 +0000
bugs1435556
milestone60.0a1
Bug 1435556 - Write changed records back to the mirror and assert the remote tree in tests. MozReview-Commit-ID: EAPgrOpnyNT
toolkit/components/places/tests/sync/head_sync.js
toolkit/components/places/tests/sync/test_bookmark_structure_changes.js
--- a/toolkit/components/places/tests/sync/head_sync.js
+++ b/toolkit/components/places/tests/sync/head_sync.js
@@ -49,16 +49,35 @@ function inspectChangeRecords(changeReco
   for (let [id, record] of Object.entries(changeRecords)) {
     (record.tombstone ? results.deleted : results.updated).push(id);
   }
   results.updated.sort();
   results.deleted.sort();
   return results;
 }
 
+async function promiseManyDatesAdded(guids) {
+  let datesAdded = new Map();
+  let db = await PlacesUtils.promiseDBConnection();
+  for (let chunk of PlacesSyncUtils.chunkArray(guids, 100)) {
+    let rows = await db.executeCached(`
+      SELECT guid, dateAdded FROM moz_bookmarks
+      WHERE guid IN (${new Array(chunk.length).fill("?").join(",")})`,
+      chunk);
+    if (rows.length != chunk.length) {
+      throw new TypeError("Can't fetch date added for nonexistent items");
+    }
+    for (let row of rows) {
+      let dateAdded = row.getResultByName("dateAdded") / 1000;
+      datesAdded.set(row.getResultByName("guid"), dateAdded);
+    }
+  }
+  return datesAdded;
+}
+
 async function assertLocalTree(rootGuid, expected, message) {
   function bookmarkNodeToInfo(node) {
     let { guid, index, title, typeCode: type } = node;
     let itemInfo = { guid, index, title, type };
     if (node.annos) {
       let syncableAnnos = node.annos.filter(anno => [
         PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO,
         PlacesSyncUtils.bookmarks.SIDEBAR_ANNO,
@@ -85,16 +104,79 @@ async function assertLocalTree(rootGuid,
   let actual = bookmarkNodeToInfo(root);
   if (!ObjectUtils.deepEqual(actual, expected)) {
     info(`Expected structure for ${rootGuid}: ${JSON.stringify(expected)}`);
     info(`Actual structure for ${rootGuid}: ${JSON.stringify(actual)}`);
     throw new Assert.constructor.AssertionError({ actual, expected, message });
   }
 }
 
+// Writes a changeset back to the mirror, simulating a successful upload.
+async function writeUploadedChanges(buf, changesToUpload) {
+  let records = Object.keys(changesToUpload).map(
+    recordId => changesToUpload[recordId].cleartext
+  );
+  await buf.store(records, { needsMerge: false });
+}
+
+async function assertRemoteTree(buf, expected, message) {
+  let itemInfos = new Map();
+
+  let itemRows = await buf.db.execute(`
+    SELECT v.guid, v.kind, v.needsMerge, IFNULL(s.position, -1) AS position,
+           v.title, u.url, v.keyword, v.tagFolderName, v.description,
+           v.loadInSidebar, v.smartBookmarkName, v.feedURL, v.siteURL
+    FROM items v
+    LEFT JOIN urls u ON u.id = v.urlId
+    LEFT JOIN structure s ON s.guid = v.guid
+    WHERE NOT v.isDeleted`);
+
+  for (let row of itemRows) {
+    let guid = row.getResultByName("guid");
+    let itemInfo = {
+      guid,
+      needsMerge: !!row.getResultByName("needsMerge"),
+    };
+    for (let name of ["kind", "position", "title", "url", "keyword",
+                      "tagFolderName", "description", "loadInSidebar",
+                      "smartBookmarkName", "feedURL", "siteURL"]) {
+      let value = row.getResultByName(name);
+      if (value !== null) {
+        itemInfo[name] = value;
+      }
+    }
+    itemInfos.set(guid, itemInfo);
+  }
+
+  function mirrorNodeToInfo(node) {
+    if (!itemInfos.has(node.guid)) {
+      throw new TypeError(`Missing item info for ${node.guid}`);
+    }
+    let itemInfo = itemInfos.get(node.guid);
+    if (node.children.length) {
+      itemInfo.children = node.children.map(mirrorNodeToInfo);
+    }
+    return itemInfo;
+  }
+
+  let remoteTree = await buf.fetchRemoteTree();
+  let actual = { root: mirrorNodeToInfo(remoteTree.root) };
+
+  let deleted = Array.from(remoteTree.deletedGuids).sort();
+  if (deleted.length) {
+    actual.deleted = deleted;
+  }
+
+  if (!ObjectUtils.deepEqual(actual, expected)) {
+    info(`Expected remote tree: ${JSON.stringify(expected)}`);
+    info(`Actual remote tree: ${JSON.stringify(actual)}`);
+    throw new Assert.constructor.AssertionError({ actual, expected, message });
+  }
+}
+
 function makeLivemarkServer() {
   let server = new HttpServer();
   server.registerPrefixHandler("/feed/", do_get_file("./livemark.xml"));
   server.start(-1);
   return {
     server,
     get site() {
       let { identity } = server;
--- a/toolkit/components/places/tests/sync/test_bookmark_structure_changes.js
+++ b/toolkit/components/places/tests/sync/test_bookmark_structure_changes.js
@@ -93,20 +93,64 @@ add_task(async function test_value_struc
 
   info("Apply remote");
   let observer = expectBookmarkChangeNotifications();
   let changesToUpload = await buf.apply({
     remoteTimeSeconds: Date.now() / 1000,
   });
   deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
 
-  let idsToUpload = inspectChangeRecords(changesToUpload);
-  deepEqual(idsToUpload, {
-    updated: ["bookmarkBBBB", "folderAAAAAA", "folderDDDDDD"],
-    deleted: [],
+  let datesAdded = await promiseManyDatesAdded(["bookmarkBBBB", "folderAAAAAA",
+    "folderDDDDDD"]);
+  deepEqual(changesToUpload, {
+    bookmarkBBBB: {
+      tombstone: false,
+      counter: 1,
+      synced: false,
+      cleartext: {
+        id: "bookmarkBBBB",
+        type: "bookmark",
+        title: "B",
+        bmkUri: "http://example.com/b",
+        parentid: "folderDDDDDD",
+        hasDupe: true,
+        parentName: "D (remote)",
+        dateAdded: datesAdded.get("bookmarkBBBB"),
+      },
+    },
+    folderAAAAAA: {
+      tombstone: false,
+      counter: 3,
+      synced: false,
+      cleartext: {
+        id: "folderAAAAAA",
+        type: "folder",
+        title: "A (local)",
+        children: ["bookmarkCCCC"],
+        parentid: "menu",
+        hasDupe: true,
+        parentName: "menu",
+        dateAdded: datesAdded.get("folderAAAAAA"),
+      },
+    },
+    folderDDDDDD: {
+      tombstone: false,
+      counter: 1,
+      synced: false,
+      cleartext: {
+        id: "folderDDDDDD",
+        type: "folder",
+        title: "D (remote)",
+        children: ["bookmarkEEEE", "bookmarkBBBB"],
+        parentid: "menu",
+        hasDupe: true,
+        parentName: "menu",
+        dateAdded: datesAdded.get("folderDDDDDD"),
+      },
+    },
   }, "Should upload records for merged and new local items");
 
   let localItemIds = await PlacesUtils.promiseManyItemIds(["folderAAAAAA",
     "bookmarkEEEE", "bookmarkBBBB", "folderDDDDDD"]);
   observer.check([{
     name: "onItemMoved",
     params: { itemId: localItemIds.get("bookmarkEEEE"),
               oldParentId: localItemIds.get("folderDDDDDD"),
@@ -167,17 +211,84 @@ add_task(async function test_value_struc
       }, {
         guid: "bookmarkBBBB",
         type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
         index: 1,
         title: "B",
         url: "http://example.com/b",
       }],
     }],
-  }, "Should reconcile structure and value changes");
+  }, "Should reconcile structure and value changes locally");
+
+  await writeUploadedChanges(buf, changesToUpload);
+  await assertRemoteTree(buf, {
+    root: {
+      guid: PlacesUtils.bookmarks.rootGuid,
+      kind: SyncedBookmarksMirror.KIND.FOLDER,
+      needsMerge: false,
+      position: -1,
+      children: [{
+        guid: PlacesUtils.bookmarks.menuGuid,
+        kind: SyncedBookmarksMirror.KIND.FOLDER,
+        needsMerge: false,
+        position: 0,
+        children: [{
+          guid: "folderAAAAAA",
+          kind: SyncedBookmarksMirror.KIND.FOLDER,
+          needsMerge: false,
+          position: 0,
+          title: "A (local)",
+          children: [{
+            guid: "bookmarkCCCC",
+            kind: SyncedBookmarksMirror.KIND.BOOKMARK,
+            needsMerge: false,
+            position: 0,
+            title: "C",
+            url: "http://example.com/c",
+          }],
+        }, {
+          guid: "folderDDDDDD",
+          kind: SyncedBookmarksMirror.KIND.FOLDER,
+          position: 1,
+          needsMerge: false,
+          title: "D (remote)",
+          children: [{
+            guid: "bookmarkEEEE",
+            kind: SyncedBookmarksMirror.KIND.BOOKMARK,
+            needsMerge: false,
+            position: 0,
+            title: "E",
+            url: "http://example.com/e",
+          }, {
+            guid: "bookmarkBBBB",
+            kind: SyncedBookmarksMirror.KIND.BOOKMARK,
+            needsMerge: false,
+            position: 1,
+            title: "B",
+            url: "http://example.com/b",
+          }],
+        }],
+      }, {
+        guid: PlacesUtils.bookmarks.toolbarGuid,
+        kind: SyncedBookmarksMirror.KIND.FOLDER,
+        needsMerge: false,
+        position: 1,
+      }, {
+        guid: PlacesUtils.bookmarks.unfiledGuid,
+        kind: SyncedBookmarksMirror.KIND.FOLDER,
+        needsMerge: false,
+        position: 2,
+      }, {
+        guid: PlacesUtils.bookmarks.mobileGuid,
+        kind: SyncedBookmarksMirror.KIND.FOLDER,
+        needsMerge: false,
+        position: 3,
+      }],
+    },
+  }, "Should update mirror with reconciled structure and value changes");
 
   await buf.finalize();
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesSyncUtils.bookmarks.reset();
 });
 
 add_task(async function test_move() {
   let buf = await openMirror("move");