Bug 1440022: Some plausible test cases r?kitcambridge draft
authorEthan Glasser-Camp <ethan@betacantrips.com>
Sun, 08 Apr 2018 11:46:18 -0400
changeset 806755 cb1d76f4d8f7b3e3be8cf642c05479c3ebaed9fc
parent 806725 9941eb8c3b29d152851220b5d9791326c35e1c68
child 806756 358909a0850d9358b499beb3efd246369b17541d
push id112944
push userbmo:eglassercamp@mozilla.com
push dateMon, 11 Jun 2018 17:13:31 +0000
reviewerskitcambridge
bugs1440022
milestone62.0a1
Bug 1440022: Some plausible test cases r?kitcambridge MozReview-Commit-ID: ITQStnQWtNX
dom/push/test/xpcshell/broadcast_handler.jsm
dom/push/test/xpcshell/head.js
dom/push/test/xpcshell/moz.build
dom/push/test/xpcshell/test_broadcast_success.js
dom/push/test/xpcshell/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/broadcast_handler.jsm
@@ -0,0 +1,16 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["broadcastHandler"];
+
+var broadcastHandler = {
+  reset() {
+    this.notifications = [];
+
+    this.wasNotified = new Promise((resolve, reject) => {
+      this.receivedBroadcastMessage = function() {
+        resolve();
+        this.notifications.push(Array.from(arguments));
+      };
+    });
+  }
+};
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -10,17 +10,19 @@ ChromeUtils.import('resource://gre/modul
 ChromeUtils.import('resource://gre/modules/PlacesUtils.jsm');
 ChromeUtils.import('resource://gre/modules/ObjectUtils.jsm');
 
 ChromeUtils.defineModuleGetter(this, 'PlacesTestUtils',
                                'resource://testing-common/PlacesTestUtils.jsm');
 XPCOMUtils.defineLazyServiceGetter(this, 'PushServiceComponent',
                                    '@mozilla.org/push/Service;1', 'nsIPushService');
 
-const serviceExports = ChromeUtils.import('resource://gre/modules/PushService.jsm', {});
+const pushServiceExports = ChromeUtils.import('resource://gre/modules/PushService.jsm', {});
+const broadcastServiceExports = ChromeUtils.import('resource://gre/modules/PushBroadcastService.jsm', {});
+const serviceExports = {...pushServiceExports, ...broadcastServiceExports};
 const servicePrefs = new Preferences('dom.push.');
 
 const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
 
 const MS_IN_ONE_DAY = 24 * 60 * 60 * 1000;
 
 var isParent = Cc['@mozilla.org/xre/runtime;1']
                  .getService(Ci.nsIXULRuntime).processType ==
--- a/dom/push/test/xpcshell/moz.build
+++ b/dom/push/test/xpcshell/moz.build
@@ -1,4 +1,8 @@
 EXTRA_COMPONENTS += [
     'PushServiceHandler.js',
     'PushServiceHandler.manifest',
 ]
+
+EXTRA_JS_MODULES += [
+    'broadcast_handler.jsm',
+]
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_broadcast_success.js
@@ -0,0 +1,265 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket, pushBroadcastService: broadcastService} = serviceExports;
+const {BroadcastService} = ChromeUtils.import("resource://gre/modules/PushBroadcastService.jsm", {});
+ChromeUtils.import("resource://gre/modules/JSONFile.jsm");
+
+ChromeUtils.import("resource://testing-common/Assert.jsm");
+ChromeUtils.import("resource://testing-common/FileTestUtils.jsm");
+ChromeUtils.import("resource://test/broadcast_handler.jsm");
+
+const assert = new Assert();
+const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
+const channelID = '0ef2ad4a-6c49-41ad-af6e-95d2425276bf';
+
+function run_test() {
+  do_get_profile();
+  setPrefs({
+    userAgentID,
+    requestTimeout: 1000,
+    retryBaseInterval: 150
+  });
+  run_next_test();
+}
+
+function getPushServiceMock() {
+  return {
+    subscribed: [],
+    subscribeBroadcast: function(broadcastId, version) {
+      this.subscribed.push([broadcastId, version]);
+    },
+  };
+}
+
+add_task(async function test_register_success() {
+  await broadcastService._resetListeners();
+  let db = PushServiceWebSocket.newPushDB();
+  broadcastHandler.reset();
+  let notifications = broadcastHandler.notifications;
+  let socket;
+  registerCleanupFunction(() => {return db.drop().then(_ => db.close());});
+
+  await broadcastService.addListener("broadcast-test", "2018-02-01", {
+    moduleURI: "resource://test/broadcast_handler.jsm",
+    symbolName: "broadcastHandler",
+  });
+
+  PushServiceWebSocket._generateID = () => channelID;
+
+  var broadcastSubscriptions = [];
+
+  await PushService.init({
+    serverURI: "wss://push.example.org/",
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(data) {
+          socket = this;
+          deepEqual(data.broadcasts, {"broadcast-test": "2018-02-01"}, "Handshake: doesn't consult listeners");
+          equal(data.messageType, 'hello', 'Handshake: wrong message type');
+          equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: userAgentID
+          }));
+        },
+
+        onBroadcastSubscribe(data) {
+          broadcastSubscriptions.push(data);
+        },
+      });
+    }
+  })
+
+  socket.serverSendMsg(JSON.stringify({
+    messageType: "broadcast",
+    broadcasts: {
+      "broadcast-test": "2018-03-02"
+    }
+  }));
+
+  await broadcastHandler.wasNotified;
+
+  deepEqual(notifications, [["2018-03-02", "broadcast-test"]], "Broadcast notification didn't get delivered");
+
+  deepEqual(await broadcastService.getListeners(), {
+    "broadcast-test": "2018-03-02"
+  }, "Broadcast version wasn't updated");
+
+  await broadcastService.addListener("example-listener", "2018-03-01", {
+    moduleURI: "resource://gre/modules/not-real-example.jsm",
+    symbolName: "doesntExist"
+  });
+
+  deepEqual(broadcastSubscriptions, [{
+    messageType: "broadcast_subscribe",
+    broadcasts: {"example-listener": "2018-03-01"}
+  }]);
+});
+
+add_task(async function test_handle_hello_broadcasts() {
+  PushService.uninit();
+  await broadcastService._resetListeners();
+  let db = PushServiceWebSocket.newPushDB();
+  broadcastHandler.reset();
+  let notifications = broadcastHandler.notifications;
+  registerCleanupFunction(() => {return db.drop().then(_ => db.close());});
+
+  await broadcastService.addListener("broadcast-test", "2018-02-01", {
+    moduleURI: "resource://test/broadcast_handler.jsm",
+    symbolName: "broadcastHandler",
+  });
+
+  PushServiceWebSocket._generateID = () => channelID;
+
+  await PushService.init({
+    serverURI: "wss://push.example.org/",
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(data) {
+          deepEqual(data.broadcasts, {"broadcast-test": "2018-02-01"}, "Handshake: doesn't consult listeners");
+          equal(data.messageType, 'hello', 'Handshake: wrong message type');
+          equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: userAgentID,
+            broadcasts: {
+              "broadcast-test": "2018-02-02"
+            }
+          }));
+        },
+
+        onBroadcastSubscribe(data) {
+          broadcastSubscriptions.push(data);
+        },
+      });
+    }
+  })
+
+  await broadcastHandler.wasNotified;
+
+  deepEqual(notifications, [["2018-02-02", "broadcast-test"]], "Broadcast notification on hello was delivered");
+
+  deepEqual(await broadcastService.getListeners(), {
+    "broadcast-test": "2018-02-02"
+  }, "Broadcast version wasn't updated");
+});
+
+add_task(async function test_broadcast_unit() {
+  const fakeListenersData = {
+    "abc": {
+      version: "2018-03-04",
+      sourceInfo: {
+        moduleURI: "resource://gre/modules/abc.jsm",
+        symbolName: "getAbc"
+      }
+    },
+    "def": {
+      version: "2018-04-05",
+      sourceInfo: {
+        moduleURI: "resource://gre/modules/def.jsm",
+        symbolName: "getDef"
+      }
+    }
+  };
+  const path = FileTestUtils.getTempFile("broadcast-listeners.json").path;
+
+  const jsonFile = new JSONFile({path});
+  jsonFile.data = {
+    listeners: fakeListenersData,
+  };
+  await jsonFile._save();
+
+  const pushServiceMock = getPushServiceMock();
+
+  const broadcastService = new BroadcastService(pushServiceMock, path);
+  const listeners = await broadcastService.getListeners();
+  deepEqual(listeners, {
+    "abc": "2018-03-04",
+    "def": "2018-04-05"
+  });
+
+  await broadcastService.addListener("ghi", "2018-05-06", {
+    moduleURI: "resource://gre/modules/ghi.jsm",
+    symbolName: "getGhi"
+  });
+
+  deepEqual(pushServiceMock.subscribed, [
+    ["ghi", "2018-05-06"]
+  ]);
+
+  await broadcastService._saveImmediately();
+
+  const newJSONFile = new JSONFile({path});
+  await newJSONFile.load();
+
+  deepEqual(newJSONFile.data, {
+    listeners: {
+      ...fakeListenersData,
+      ghi: {
+        version: "2018-05-06",
+        sourceInfo: {
+          moduleURI: "resource://gre/modules/ghi.jsm",
+          symbolName: "getGhi"
+        }
+      }
+    },
+    version: 1,
+  });
+
+  deepEqual(await broadcastService.getListeners(), {
+    "abc": "2018-03-04",
+    "def": "2018-04-05",
+    "ghi": "2018-05-06"
+  });
+});
+
+add_task(async function test_broadcast_initialize_sane() {
+  const path = FileTestUtils.getTempFile("broadcast-listeners.json").path;
+  const broadcastService = new BroadcastService(getPushServiceMock(), path);
+  deepEqual(await broadcastService.getListeners(), {}, "listeners should start out sane");
+  await broadcastService._saveImmediately();
+  let onDiskJSONFile = new JSONFile({path});
+  await onDiskJSONFile.load();
+  deepEqual(onDiskJSONFile.data, {listeners: {}, version: 1},
+            "written JSON file has listeners and version fields");
+
+  await broadcastService.addListener("ghi", "2018-05-06", {
+    moduleURI: "resource://gre/modules/ghi.jsm",
+    symbolName: "getGhi"
+  });
+
+  await broadcastService._saveImmediately();
+
+  onDiskJSONFile = new JSONFile({path});
+  await onDiskJSONFile.load();
+
+  deepEqual(onDiskJSONFile.data, {
+    listeners: {
+      ghi: {
+        version: "2018-05-06",
+        sourceInfo: {
+          moduleURI: "resource://gre/modules/ghi.jsm",
+          symbolName: "getGhi"
+        }
+      }
+    },
+    version: 1,
+  }, "adding listeners to initial state is written OK");
+});
+
+add_task(async function test_broadcast_reject_invalid_sourceinfo() {
+  const path = FileTestUtils.getTempFile("broadcast-listeners.json").path;
+  const broadcastService = new BroadcastService(getPushServiceMock(), path);
+
+  await assert.rejects(broadcastService.addListener("ghi", "2018-05-06", {
+      moduleName: "resource://gre/modules/ghi.jsm",
+      symbolName: "getGhi"
+  }), "missing moduleURI", "rejects sourceInfo that doesn't have moduleURI");
+});
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 head = head.js head-http2.js
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
+support-files = broadcast_handler.jsm
 
+[test_broadcast_success.js]
 [test_clear_forgetAboutSite.js]
 [test_clear_origin_data.js]
 [test_crypto.js]
 [test_crypto_encrypt.js]
 [test_drop_expired.js]
 [test_handler_service.js]
 support-files = PushServiceHandler.js PushServiceHandler.manifest
 [test_notification_ack.js]