summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_bridged_engine.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_bridged_engine.js')
-rw-r--r--services/sync/tests/unit/test_bridged_engine.js248
1 files changed, 248 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_bridged_engine.js b/services/sync/tests/unit/test_bridged_engine.js
new file mode 100644
index 0000000000..25a81f8f69
--- /dev/null
+++ b/services/sync/tests/unit/test_bridged_engine.js
@@ -0,0 +1,248 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { BridgedEngine, BridgeWrapperXPCOM } = ChromeUtils.importESModule(
+ "resource://services-sync/bridged_engine.sys.mjs"
+);
+const { Service } = ChromeUtils.importESModule(
+ "resource://services-sync/service.sys.mjs"
+);
+
+// Wraps an `object` in a proxy so that its methods are bound to it. This
+// simulates how XPCOM class instances have all their methods bound.
+function withBoundMethods(object) {
+ return new Proxy(object, {
+ get(target, key) {
+ let value = target[key];
+ return typeof value == "function" ? value.bind(target) : value;
+ },
+ });
+}
+
+add_task(async function test_interface() {
+ class TestBridge {
+ constructor() {
+ this.storageVersion = 2;
+ this.syncID = "syncID111111";
+ this.clear();
+ }
+
+ clear() {
+ this.lastSyncMillis = 0;
+ this.wasSyncStarted = false;
+ this.incomingEnvelopes = [];
+ this.uploadedIDs = [];
+ this.wasSyncFinished = false;
+ this.wasReset = false;
+ this.wasWiped = false;
+ }
+
+ // `mozIBridgedSyncEngine` methods.
+
+ getLastSync(callback) {
+ CommonUtils.nextTick(() => callback.handleSuccess(this.lastSyncMillis));
+ }
+
+ setLastSync(millis, callback) {
+ this.lastSyncMillis = millis;
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ resetSyncId(callback) {
+ CommonUtils.nextTick(() => callback.handleSuccess(this.syncID));
+ }
+
+ ensureCurrentSyncId(newSyncId, callback) {
+ equal(newSyncId, this.syncID, "Local and new sync IDs should match");
+ CommonUtils.nextTick(() => callback.handleSuccess(this.syncID));
+ }
+
+ syncStarted(callback) {
+ this.wasSyncStarted = true;
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ storeIncoming(envelopes, callback) {
+ this.incomingEnvelopes.push(...envelopes.map(r => JSON.parse(r)));
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ apply(callback) {
+ let outgoingEnvelopes = [
+ {
+ id: "hanson",
+ data: {
+ plants: ["seed", "flower 💐", "rose"],
+ canYouTell: false,
+ },
+ },
+ {
+ id: "sheryl-crow",
+ data: {
+ today: "winding 🛣",
+ tomorrow: "winding 🛣",
+ },
+ },
+ ].map(cleartext =>
+ JSON.stringify({
+ id: cleartext.id,
+ payload: JSON.stringify(cleartext),
+ })
+ );
+ CommonUtils.nextTick(() => callback.handleSuccess(outgoingEnvelopes));
+ }
+
+ setUploaded(millis, ids, callback) {
+ this.uploadedIDs.push(...ids);
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ syncFinished(callback) {
+ this.wasSyncFinished = true;
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ reset(callback) {
+ this.clear();
+ this.wasReset = true;
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+
+ wipe(callback) {
+ this.clear();
+ this.wasWiped = true;
+ CommonUtils.nextTick(() => callback.handleSuccess());
+ }
+ }
+
+ let bridge = new TestBridge();
+ let engine = new BridgedEngine("Nineties", Service);
+ engine._bridge = new BridgeWrapperXPCOM(withBoundMethods(bridge));
+ engine.enabled = true;
+
+ let server = await serverForFoo(engine);
+ try {
+ await SyncTestingInfrastructure(server);
+
+ info("Add server records");
+ let foo = server.user("foo");
+ let collection = foo.collection("nineties");
+ let now = new_timestamp();
+ collection.insert(
+ "backstreet",
+ encryptPayload({
+ id: "backstreet",
+ data: {
+ say: "I want it that way",
+ when: "never",
+ },
+ }),
+ now
+ );
+ collection.insert(
+ "tlc",
+ encryptPayload({
+ id: "tlc",
+ data: {
+ forbidden: ["scrubs 🚫"],
+ numberAvailable: false,
+ },
+ }),
+ now + 5
+ );
+
+ info("Sync the engine");
+ // Advance the last sync time to skip the Backstreet Boys...
+ bridge.lastSyncMillis = 1000 * (now + 2);
+ await sync_engine_and_validate_telem(engine, false);
+
+ let metaGlobal = foo.collection("meta").wbo("global").get();
+ deepEqual(
+ JSON.parse(metaGlobal.payload).engines.nineties,
+ {
+ version: 2,
+ syncID: "syncID111111",
+ },
+ "Should write storage version and sync ID to m/g"
+ );
+
+ greater(bridge.lastSyncMillis, 0, "Should update last sync time");
+ ok(
+ bridge.wasSyncStarted,
+ "Should have started sync before storing incoming"
+ );
+ deepEqual(
+ bridge.incomingEnvelopes
+ .sort((a, b) => a.id.localeCompare(b.id))
+ .map(({ payload, ...envelope }) => ({
+ cleartextAsObject: JSON.parse(payload),
+ ...envelope,
+ })),
+ [
+ {
+ id: "tlc",
+ modified: now + 5,
+ cleartextAsObject: {
+ id: "tlc",
+ data: {
+ forbidden: ["scrubs 🚫"],
+ numberAvailable: false,
+ },
+ },
+ },
+ ],
+ "Should stage incoming records from server"
+ );
+ deepEqual(
+ bridge.uploadedIDs.sort(),
+ ["hanson", "sheryl-crow"],
+ "Should mark new local records as uploaded"
+ );
+ ok(bridge.wasSyncFinished, "Should have finished sync after uploading");
+
+ deepEqual(
+ collection.keys().sort(),
+ ["backstreet", "hanson", "sheryl-crow", "tlc"],
+ "Should have all records on server"
+ );
+ let expectedRecords = [
+ {
+ id: "sheryl-crow",
+ data: {
+ today: "winding 🛣",
+ tomorrow: "winding 🛣",
+ },
+ },
+ {
+ id: "hanson",
+ data: {
+ plants: ["seed", "flower 💐", "rose"],
+ canYouTell: false,
+ },
+ },
+ ];
+ for (let expected of expectedRecords) {
+ let actual = collection.cleartext(expected.id);
+ deepEqual(
+ actual,
+ expected,
+ `Should upload record ${expected.id} from bridged engine`
+ );
+ }
+
+ await engine.resetClient();
+ ok(bridge.wasReset, "Should reset local storage for bridge");
+
+ await engine.wipeClient();
+ ok(bridge.wasWiped, "Should wipe local storage for bridge");
+
+ await engine.resetSyncID();
+ ok(
+ !foo.collection("nineties"),
+ "Should delete server collection after resetting sync ID"
+ );
+ } finally {
+ await promiseStopServer(server);
+ await engine.finalize();
+ }
+});