Bug 1150812 - xcpshell test for PushService with http2. r=nsm, r=mt
authorDragana Damjanovic <dd.mozilla@gmail.com>
Tue, 02 Jun 2015 07:16:00 -0400
changeset 269684 4c897e262cd53d5cc043927ae1c75be20f395205
parent 269683 db9050215b8a3ee9c044b9ffce055a399b7d7b34
child 269685 b2740c7080d364c5cb4fe0d6cb706ab3fdbae882
push id2540
push userwcosta@mozilla.com
push dateWed, 03 Jun 2015 20:55:41 +0000
reviewersnsm, mt
bugs1150812
milestone41.0a1
Bug 1150812 - xcpshell test for PushService with http2. r=nsm, r=mt
dom/push/PushService.jsm
dom/push/test/xpcshell/head-http2.js
dom/push/test/xpcshell/head.js
dom/push/test/xpcshell/test_notification_http2.js
dom/push/test/xpcshell/test_register_5xxCode_http2.js
dom/push/test/xpcshell/test_register_error_http2.js
dom/push/test/xpcshell/test_register_success_http2.js
dom/push/test/xpcshell/test_registration_error_http2.js
dom/push/test/xpcshell/test_registration_success_http2.js
dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js
dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js
dom/push/test/xpcshell/test_unregister_success_http2.js
dom/push/test/xpcshell/xpcshell.ini
testing/xpcshell/moz-http2/moz-http2.js
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -375,22 +375,29 @@ this.PushService = {
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
 
     if (options.serverURI) {
       // this is use for xpcshell test.
 
       var uri;
       var service;
-      for (let connProtocol of CONNECTION_PROTOCOLS) {
-        uri = connProtocol.checkServerURI(options.serverURI);
-        if (uri) {
-          service = connProtocol;
-          break;
+      if (!options.service) {
+        for (let connProtocol of CONNECTION_PROTOCOLS) {
+          uri = connProtocol.checkServerURI(options.serverURI);
+          if (uri) {
+            service = connProtocol;
+            break;
+          }
         }
+      } else {
+        try {
+          uri  = Services.io.newURI(options.serverURI, null, null);
+          service = options.service;
+        } catch(e) {}
       }
       if (!service) {
         this._setState(PUSH_SERVICE_INIT);
         return;
       }
 
       // Start service.
       this._startService(service, uri, false, options);
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/head-http2.js
@@ -0,0 +1,51 @@
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+  this.host = host;
+  this.port = port || 443;
+  this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+  host: null,
+  bits: null,
+
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIBadCertListener2) ||
+        aIID.equals(Ci.nsIInterfaceRequestor) ||
+        aIID.equals(Ci.nsISupports))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+    var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+    var cos = Cc["@mozilla.org/security/certoverride;1"].
+              getService(Ci.nsICertOverrideService);
+    cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+    dump("Certificate Override in place\n");
+    return true;
+  },
+};
+
+function addCertOverride(host, port, bits) {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  try {
+    var url;
+    if (port && (port > 0) && (port !== 443)) {
+      url = "https://" + host + ":" + port + "/";
+    } else {
+      url = "https://" + host + "/";
+    }
+    req.open("GET", url, false);
+    req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+    req.send(null);
+  } catch (e) {
+    // This will fail since the server is not trusted yet
+  }
+}
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -191,17 +191,20 @@ function setPrefs(prefs = {}) {
     'pingInterval.mobile': 3 * 60 * 1000,
     'pingInterval.wifi': 3 * 60 * 1000,
     'adaptive.lastGoodPingInterval': 3 * 60 * 1000,
     'adaptive.lastGoodPingInterval.mobile': 3 * 60 * 1000,
     'adaptive.lastGoodPingInterval.wifi': 3 * 60 * 1000,
     'adaptive.gap': 60000,
     'adaptive.upperLimit': 29 * 60 * 1000,
     // Misc. defaults.
-    'adaptive.mobile': ''
+    'adaptive.mobile': '',
+    'http2.maxRetries': 2,
+    'http2.retryInterval': 500,
+    'http2.reset_retry_count_after_ms': 60000
   }, prefs);
   for (let pref in defaultPrefs) {
     servicePrefs.set(pref, defaultPrefs[pref]);
   }
 }
 
 function compareAscending(a, b) {
   return a > b ? 1 : a < b ? -1 : 0;
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var prefs;
+var tlsProfile;
+
+var serverPort = -1;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  serverPort = env.get("MOZHTTP2-PORT");
+  do_check_neq(serverPort, null);
+  dump("using port " + serverPort + "\n");
+
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+  // Set to allow the cert presented by our H2 server
+  var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
+  prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+
+  addCertOverride("localhost", serverPort,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
+
+  disableServiceWorkerEvents(
+    'https://example.com/page/1',
+    'https://example.com/page/2',
+    'https://example.com/page/3'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test_pushNotifications() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  var serverURL = "https://localhost:" + serverPort;
+
+  let records = [{
+    subscriptionUri: serverURL + '/pushNotifications/subscription1',
+    pushEndpoint: serverURL + '/pushEndpoint1',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint1',
+    scope: 'https://example.com/page/1'
+  }, {
+    subscriptionUri: serverURL + '/pushNotifications/subscription2',
+    pushEndpoint: serverURL + '/pushEndpoint2',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint2',
+    scope: 'https://example.com/page/2'
+  }, {
+    subscriptionUri: serverURL + '/pushNotifications/subscription3',
+    pushEndpoint: serverURL + '/pushEndpoint3',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint3',
+    scope: 'https://example.com/page/3'
+  }];
+
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  let notifyPromise = Promise.all([
+    promiseObserverNotification('push-notification'),
+    promiseObserverNotification('push-notification'),
+    promiseObserverNotification('push-notification')
+  ]);
+
+  PushService.init({
+    serverURI: serverURL,
+    db
+  });
+
+  yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for notifications');
+});
+
+add_task(function* test_complete() {
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var httpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
+  return httpServer.identity.primaryPort;
+});
+
+var retries = 0
+
+function subscribe5xxCodeHandler(metadata, response) {
+  if (retries == 0) {
+    ok(true, "Subscribe 5xx code");
+    do_test_finished();
+    response.setHeader("Retry-After", '1');
+    response.setStatusLine(metadata.httpVersion, 500, "Retry");
+  } else {
+    ok(true, "Subscribed");
+    do_test_finished();
+    response.setHeader("Location",
+                       'http://localhost:' + serverPort + '/subscription')
+    response.setHeader("Link",
+                       '</pushEndpoint>; rel="urn:ietf:params:push", ' +
+                       '</receiptPushEndpoint>; rel="urn:ietf:params:push:receipt"');
+    response.setStatusLine(metadata.httpVersion, 201, "OK");
+  }
+  retries++;
+}
+
+function listenSuccessHandler(metadata, response) {
+  do_check_true(true, "New listener point");
+  ok(retries == 2, "Should try 2 times.");
+  do_test_finished();
+  response.setHeader("Retry-After", '10');
+  response.setStatusLine(metadata.httpVersion, 500, "Retry");
+}
+
+
+httpServer = new HttpServer();
+httpServer.registerPathHandler("/subscribe5xxCode", subscribe5xxCodeHandler);
+httpServer.registerPathHandler("/subscription", listenSuccessHandler);
+httpServer.start(-1);
+
+function run_test() {
+
+  do_get_profile();
+  setPrefs({
+    'http2.retryInterval': 1000,
+    'http2.maxRetries': 2
+  });
+  disableServiceWorkerEvents(
+    'https://example.com/retry5xxCode'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+
+  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
+
+  PushService.init({
+    serverURI: serverURL + "/subscribe5xxCode",
+    service: PushServiceHttp2,
+    db
+  });
+
+  let newRecord = yield PushNotificationService.register(
+    'https://example.com/retry5xxCode'
+  );
+
+  var subscriptionUri = serverURL + '/subscription';
+  var pushEndpoint = serverURL + '/pushEndpoint';
+  var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint';
+  equal(newRecord.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in registration record');
+  equal(newRecord.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in registration record');
+
+  equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in registration record');
+  equal(newRecord.scope, 'https://example.com/retry5xxCode',
+    'Wrong scope in registration record');
+
+  let record = yield db.getByKeyID(subscriptionUri);
+  equal(record.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in database record');
+  equal(record.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in database record');
+  equal(record.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in database record');
+  equal(record.scope, 'https://example.com/retry5xxCode',
+    'Wrong scope in database record');
+
+  httpServer.stop(do_test_finished);
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_register_error_http2.js
@@ -0,0 +1,207 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var prefs;
+var tlsProfile;
+var serverURL;
+
+var serverPort = -1;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  serverPort = env.get("MOZHTTP2-PORT");
+  do_check_neq(serverPort, null);
+
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+  disableServiceWorkerEvents(
+    'https://example.net/page/invalid-response'
+  );
+
+  serverURL = "https://localhost:" + serverPort;
+
+  run_next_test();
+}
+
+// Connection will fail because of the certificates.
+add_task(function* test_pushSubscriptionNoConnection() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionNoConnection/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Error");
+    },
+    'Wrong error for not being able to establish connecion.'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, "Should not store records when connection couldn't be established.");
+  PushService.uninit();
+});
+
+add_task(function* test_TLS() {
+    // Set to allow the cert presented by our H2 server
+  var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
+  prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+
+  addCertOverride("localhost", serverPort,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
+});
+
+add_task(function* test_pushSubscriptionMissingLocation() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionMissingLocation/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Return code 201, but the answer is bogus");
+    },
+    'Wrong error for the missing location header.'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, 'Should not store records when the location header is missing.');
+  PushService.uninit();
+});
+
+add_task(function* test_pushSubscriptionMissingLink() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionMissingLink/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Return code 201, but the answer is bogus");
+    },
+    'Wrong error for the missing link header.'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, 'Should not store records when a link header is missing.');
+  PushService.uninit();
+});
+
+add_task(function* test_pushSubscriptionMissingLink1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionMissingLink1/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Return code 201, but the answer is bogus");
+    },
+    'Wrong error for the missing push endpoint.'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, 'Should not store records when the push endpoint is missing.');
+  PushService.uninit();
+});
+
+add_task(function* test_pushSubscriptionLocationBogus() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionLocationBogus/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Return code 201, but URI is bogus.");
+    },
+    'Wrong error for the bogus location'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, 'Should not store records when location header is bogus.');
+  PushService.uninit();
+});
+
+add_task(function* test_pushSubscriptionNot2xxCode() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionNot201Code/subscribe",
+    db
+  });
+
+  yield rejects(
+    PushNotificationService.register(
+      'https://example.net/page/invalid-response'),
+    function(error) {
+      return error && error.includes("Error");
+    },
+    'Wrong error for not 201 responce code.'
+  );
+
+  let record = yield db.getAllKeyIDs();
+  ok(record.length === 0, 'Should not store records when respons code is not 201.');
+});
+
+add_task(function* test_complete() {
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_register_success_http2.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var prefs;
+var tlsProfile;
+var serverURL;
+var serverPort = -1;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  serverPort = env.get("MOZHTTP2-PORT");
+  do_check_neq(serverPort, null);
+
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+  // Set to allow the cert presented by our H2 server
+  var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
+  prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+
+  addCertOverride("localhost", serverPort,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
+
+  serverURL = "https://localhost:" + serverPort;
+
+  disableServiceWorkerEvents(
+    'https://example.org/1',
+    'https://example.org/no_receiptEndpoint'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test_pushSubscriptionSuccess() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionSuccess/subscribe",
+    db
+  });
+
+  let newRecord = yield PushNotificationService.register(
+    'https://example.org/1'
+  );
+
+  var subscriptionUri = serverURL + '/pushSubscriptionSuccesss';
+  var pushEndpoint = serverURL + '/pushEndpointSuccess';
+  var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess';
+  equal(newRecord.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in registration record');
+  equal(newRecord.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in registration record');
+
+  equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in registration record');
+  equal(newRecord.scope, 'https://example.org/1',
+    'Wrong scope in registration record');
+
+  let record = yield db.getByKeyID(subscriptionUri);
+  equal(record.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in database record');
+  equal(record.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in database record');
+  equal(record.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in database record');
+  equal(record.scope, 'https://example.org/1',
+    'Wrong scope in database record');
+
+  db.drop().then(PushService.uninit());
+});
+
+add_task(function* test_pushSubscriptionMissingLink2() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  PushService.init({
+    serverURI: serverURL + "/pushSubscriptionMissingLink2/subscribe",
+    db
+  });
+
+  let newRecord = yield PushNotificationService.register(
+    'https://example.org/no_receiptEndpoint'
+  );
+
+  var subscriptionUri = serverURL + '/subscriptionMissingLink2';
+  var pushEndpoint = serverURL + '/pushEndpointMissingLink2';
+  var pushReceiptEndpoint = '';
+  equal(newRecord.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in registration record');
+  equal(newRecord.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in registration record');
+
+  equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in registration record');
+  equal(newRecord.scope, 'https://example.org/no_receiptEndpoint',
+    'Wrong scope in registration record');
+
+  let record = yield db.getByKeyID(subscriptionUri);
+  equal(record.subscriptionUri, subscriptionUri,
+    'Wrong subscription ID in database record');
+  equal(record.pushEndpoint, pushEndpoint,
+    'Wrong push endpoint in database record');
+  equal(record.pushReceiptEndpoint, pushReceiptEndpoint,
+    'Wrong push endpoint receipt in database record');
+  equal(record.scope, 'https://example.org/no_receiptEndpoint',
+    'Wrong scope in database record');
+});
+
+add_task(function* test_complete() {
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_registration_error_http2.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+function run_test() {
+  do_get_profile();
+  run_next_test();
+}
+
+add_task(function* test_registrations_error() {
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  PushService.init({
+    serverURI: "https://push.example.org/",
+    networkInfo: new MockDesktopNetworkInfo(),
+    db: makeStub(db, {
+      getByScope(prev, scope) {
+        return Promise.reject('Database error');
+      }
+    }),
+  });
+
+  yield rejects(
+    PushNotificationService.registration('https://example.net/1'),
+    function(error) {
+      return error == 'Database error';
+    },
+    'Wrong message'
+  );
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_registration_success_http2.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var prefs;
+
+var serverPort = -1;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  serverPort = env.get("MOZHTTP2-PORT");
+  do_check_neq(serverPort, null);
+
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  disableServiceWorkerEvents(
+    'https://example.net/a',
+    'https://example.net/b',
+    'https://example.net/c'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test_pushNotifications() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  var serverURL = "https://localhost:" + serverPort;
+
+  let records = [{
+    subscriptionUri: serverURL + '/subscriptionA',
+    pushEndpoint: serverURL + '/pushEndpointA',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpointA',
+    scope: 'https://example.net/a'
+  }, {
+    subscriptionUri: serverURL + '/subscriptionB',
+    pushEndpoint: serverURL + '/pushEndpointB',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpointB',
+    scope: 'https://example.net/b'
+  }, {
+    subscriptionUri: serverURL + '/subscriptionC',
+    pushEndpoint: serverURL + '/pushEndpointC',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpointC',
+    scope: 'https://example.net/c'
+  }];
+
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  PushService.init({
+    serverURI: serverURL,
+    db
+  });
+
+  let registration = yield PushNotificationService.registration(
+    'https://example.net/a');
+  equal(
+    registration.pushEndpoint,
+    serverURL + '/pushEndpointA',
+    'Wrong push endpoint for scope'
+  );
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var httpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
+  return httpServer.identity.primaryPort;
+});
+
+function listen4xxCodeHandler(metadata, response) {
+  ok(true, "Listener point error")
+  do_test_finished();
+  response.setStatusLine(metadata.httpVersion, 410, "GONE");
+}
+
+function resubscribeHandler(metadata, response) {
+  ok(true, "Ask for new subscription");
+  do_test_finished();
+  response.setHeader("Location",
+                  'http://localhost:' + serverPort + '/newSubscription')
+  response.setHeader("Link",
+                  '</newPushEndpoint>; rel="urn:ietf:params:push", ' +
+                  '</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"');
+  response.setStatusLine(metadata.httpVersion, 201, "OK");
+}
+
+function listenSuccessHandler(metadata, response) {
+  do_check_true(true, "New listener point");
+  httpServer.stop(do_test_finished);
+  response.setStatusLine(metadata.httpVersion, 204, "Try again");
+}
+
+
+httpServer = new HttpServer();
+httpServer.registerPathHandler("/subscription4xxCode", listen4xxCodeHandler);
+httpServer.registerPathHandler("/subscribe", resubscribeHandler);
+httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
+httpServer.start(-1);
+
+function run_test() {
+
+  do_get_profile();
+
+  disableServiceWorkerEvents(
+    'https://example.com/page'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+
+  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
+
+  let records = [{
+    subscriptionUri: serverURL + '/subscription4xxCode',
+    pushEndpoint: serverURL + '/pushEndpoint',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint',
+    scope: 'https://example.com/page'
+  }];
+
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  PushService.init({
+    serverURI: serverURL + "/subscribe",
+    service: PushServiceHttp2,
+    db
+  });
+
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var httpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
+  return httpServer.identity.primaryPort;
+});
+
+var retries = 0
+
+function listen5xxCodeHandler(metadata, response) {
+  ok(true, "Listener 5xx code");
+  do_test_finished();
+  retries++;
+  response.setHeader("Retry-After", '1');
+  response.setStatusLine(metadata.httpVersion, 500, "Retry");
+}
+
+function resubscribeHandler(metadata, response) {
+  ok(true, "Ask for new subscription");
+  ok(retries == 3, "Should retry 2 times.");
+  do_test_finished();
+  response.setHeader("Location",
+                  'http://localhost:' + serverPort + '/newSubscription')
+  response.setHeader("Link",
+                  '</newPushEndpoint>; rel="urn:ietf:params:push", ' +
+                  '</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"');
+  response.setStatusLine(metadata.httpVersion, 201, "OK");
+}
+
+function listenSuccessHandler(metadata, response) {
+  do_check_true(true, "New listener point");
+  httpServer.stop(do_test_finished);
+  response.setStatusLine(metadata.httpVersion, 204, "Try again");
+}
+
+
+httpServer = new HttpServer();
+httpServer.registerPathHandler("/subscription5xxCode", listen5xxCodeHandler);
+httpServer.registerPathHandler("/subscribe", resubscribeHandler);
+httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
+httpServer.start(-1);
+
+function run_test() {
+
+  do_get_profile();
+  setPrefs({
+    'http2.retryInterval': 1000,
+    'http2.maxRetries': 2
+  });
+  disableServiceWorkerEvents(
+    'https://example.com/page'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+
+  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
+
+  let records = [{
+    subscriptionUri: serverURL + '/subscription5xxCode',
+    pushEndpoint: serverURL + '/pushEndpoint',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint',
+    scope: 'https://example.com/page'
+  }];
+
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  PushService.init({
+    serverURI: serverURL + "/subscribe",
+    service: PushServiceHttp2,
+    db
+  });
+
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var httpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
+  return httpServer.identity.primaryPort;
+});
+
+function resubscribeHandler(metadata, response) {
+  ok(true, "Ask for new subscription");
+  do_test_finished();
+  response.setHeader("Location",
+                  'http://localhost:' + serverPort + '/newSubscription')
+  response.setHeader("Link",
+                  '</newPushEndpoint>; rel="urn:ietf:params:push", ' +
+                  '</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"');
+  response.setStatusLine(metadata.httpVersion, 201, "OK");
+}
+
+function listenSuccessHandler(metadata, response) {
+  do_check_true(true, "New listener point");
+  httpServer.stop(do_test_finished);
+  response.setStatusLine(metadata.httpVersion, 204, "Try again");
+}
+
+
+httpServer = new HttpServer();
+httpServer.registerPathHandler("/subscribe", resubscribeHandler);
+httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
+httpServer.start(-1);
+
+function run_test() {
+
+  do_get_profile();
+  setPrefs({
+    'http2.retryInterval': 1000,
+    'http2.maxRetries': 2
+  });
+  disableServiceWorkerEvents(
+    'https://example.com/page'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  do_test_pending();
+  do_test_pending();
+
+  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
+
+  let records = [{
+    subscriptionUri: 'http://localhost/subscriptionNotExist',
+    pushEndpoint: serverURL + '/pushEndpoint',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint',
+    scope: 'https://example.com/page'
+  }];
+
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  PushService.init({
+    serverURI: serverURL + "/subscribe",
+    service: PushServiceHttp2,
+    db
+  });
+
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_unregister_success_http2.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var prefs;
+var tlsProfile;
+
+var serverPort = -1;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  serverPort = env.get("MOZHTTP2-PORT");
+  do_check_neq(serverPort, null);
+
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+  // Set to allow the cert presented by our H2 server
+  var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
+  prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+
+  addCertOverride("localhost", serverPort,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
+
+  disableServiceWorkerEvents(
+    'https://example.com/page/unregister-success'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test_pushUnsubscriptionSuccess() {
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(() => {
+    return db.drop().then(_ => db.close());
+  });
+
+  var serverURL = "https://localhost:" + serverPort;
+
+  yield db.put({
+    subscriptionUri: serverURL + '/subscriptionUnsubscriptionSuccess',
+    pushEndpoint: serverURL + '/pushEndpointUnsubscriptionSuccess',
+    pushReceiptEndpoint: serverURL + '/receiptPushEndpointUnsubscriptionSuccess',
+    scope: 'https://example.com/page/unregister-success'
+  });
+
+  PushService.init({
+    serverURI: serverURL,
+    db
+  });
+
+  yield PushNotificationService.unregister(
+    'https://example.com/page/unregister-success');
+  let record = yield db.getByKeyID(serverURL + '/subscriptionUnsubscriptionSuccess');
+  ok(!record, 'Unregister did not remove record');
+
+});
+
+add_task(function* test_complete() {
+  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
+});
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,16 +1,14 @@
 [DEFAULT]
-head = head.js
+head = head.js head-http2.js
 tail =
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
 
-[test_clearAll_successful.js]
-run-sequentially = This will delete all existing push subscritions.
 [test_notification_ack.js]
 [test_notification_duplicate.js]
 [test_notification_error.js]
 [test_notification_incomplete.js]
 [test_notification_version_string.js]
 [test_register_case.js]
 [test_register_flush.js]
 [test_register_invalid_channel.js]
@@ -27,8 +25,34 @@ run-sequentially = This will delete all 
 [test_registration_missing_scope.js]
 [test_registration_none.js]
 [test_registration_success.js]
 [test_unregister_empty_scope.js]
 [test_unregister_error.js]
 [test_unregister_invalid_json.js]
 [test_unregister_not_found.js]
 [test_unregister_success.js]
+#http2 test
+[test_resubscribe_4xxCode_http2.js]
+[test_resubscribe_5xxCode_http2.js]
+[test_resubscribe_listening_for_msg_error_http2.js]
+[test_register_5xxCode_http2.js]
+[test_register_success_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_register_error_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_unregister_success_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_notification_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_registration_success_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_registration_error_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_clearAll_successful.js]
+skip-if = !hasNode
+run-sequentially = This will delete all existing push subscritions.
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -106,16 +106,19 @@ var h11required_header = "yes";
 var didRst = false;
 var rstConnection = null;
 
 function handleRequest(req, res) {
   var u = url.parse(req.url);
   var content = getHttpContent(u.pathname);
   var push, push1, push1a, push2, push3;
 
+  // PushService tests.
+  var pushPushServer1, pushPushServer2, pushPushServer3;
+
   if (req.httpVersionMajor === 2) {
     res.setHeader('X-Connection-Http2', 'yes');
     res.setHeader('X-Http2-StreamId', '' + req.stream.id);
   } else {
     res.setHeader('X-Connection-Http2', 'no');
   }
 
   if (u.pathname === '/exit') {
@@ -397,16 +400,143 @@ function handleRequest(req, res) {
   }
 
   // for use with test_altsvc.js
   else if (u.pathname === "/altsvc-test") {
     res.setHeader('Cache-Control', 'no-cache');
     res.setHeader('Alt-Svc', 'h2=' + req.headers['x-altsvc']);
   }
 
+  // for PushService tests.
+  else if (u.pathname === "/pushSubscriptionSuccess/subscribe") {
+    res.setHeader("Location",
+                  'https://localhost:' + serverPort + '/pushSubscriptionSuccesss');
+    res.setHeader("Link",
+                  '</pushEndpointSuccess>; rel="urn:ietf:params:push", ' +
+                  '</receiptPushEndpointSuccess>; rel="urn:ietf:params:push:receipt"');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionSuccesss") {
+    // do nothing.
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionMissingLocation/subscribe") {
+    res.setHeader("Link",
+                  '</pushEndpointMissingLocation>; rel="urn:ietf:params:push", ' +
+                  '</receiptPushEndpointMissingLocation>; rel="urn:ietf:params:push:receipt"');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionMissingLink/subscribe") {
+    res.setHeader("Location",
+                  'https://localhost:' + serverPort + '/subscriptionMissingLink');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionLocationBogus/subscribe") {
+    res.setHeader("Location", '1234');
+    res.setHeader("Link",
+                  '</pushEndpointLocationBogus; rel="urn:ietf:params:push", ' +
+                  '</receiptPushEndpointLocationBogus>; rel="urn:ietf:params:push:receipt"');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionMissingLink1/subscribe") {
+    res.setHeader("Location",
+                  'https://localhost:' + serverPort + '/subscriptionMissingLink1');
+    res.setHeader("Link",
+                  '</receiptPushEndpointMissingLink1>; rel="urn:ietf:params:push:receipt"');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionMissingLink2/subscribe") {
+    res.setHeader("Location",
+                  'https://localhost:' + serverPort + '/subscriptionMissingLink2');
+    res.setHeader("Link",
+                  '</pushEndpointMissingLink2>; rel="urn:ietf:params:push"');
+    res.writeHead(201, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname === "/subscriptionMissingLink2") {
+    // do nothing.
+    return;
+  }
+
+  else if (u.pathname === "/pushSubscriptionNot201Code/subscribe") {
+    res.setHeader("Location",
+                  'https://localhost:' + serverPort + '/subscriptionNot2xxCode');
+    res.setHeader("Link",
+                  '</pushEndpointNot201Code>; rel="urn:ietf:params:push", ' +
+                  '</receiptPushEndpointNot201Code>; rel="urn:ietf:params:push:receipt"');
+    res.writeHead(200, "OK");
+    res.end("");
+    return;
+  }
+
+  else if (u.pathname ==="/pushNotifications/subscription1") {
+    pushPushServer1 = res.push(
+        { hostname: 'localhost:' + serverPort, port: serverPort,
+          path : '/pushNotificationsDeliver1', method : 'GET',
+          headers: {'x-pushed-request': 'true', 'x-foo' : 'bar'}});
+    pushPushServer1.writeHead(200, {
+      'content-length' : 2,
+      'subresource' : '1'
+      });
+    pushPushServer1.end('ok');
+    return;
+  }
+
+  else if (u.pathname ==="/pushNotifications/subscription2") {
+    pushPushServer2 = res.push(
+        { hostname: 'localhost:' + serverPort, port: serverPort,
+          path : '/pushNotificationsDeliver3', method : 'GET',
+          headers: {'x-pushed-request': 'true', 'x-foo' : 'bar'}});
+    pushPushServer2.writeHead(200, {
+      'content-length' : 2,
+      'subresource' : '1'
+      });
+    pushPushServer2.end('ok');
+    return;
+  }
+
+  else if (u.pathname ==="/pushNotifications/subscription3") {
+    pushPushServer3 = res.push(
+        { hostname: 'localhost:' + serverPort, port: serverPort,
+          path : '/pushNotificationsDeliver3', method : 'GET',
+          headers: {'x-pushed-request': 'true', 'x-foo' : 'bar'}});
+    pushPushServer3.writeHead(200, {
+      'content-length' : 2,
+      'subresource' : '1'
+      });
+    pushPushServer3.end('ok');
+    return;
+  }
+
+  else if ((u.pathname === "/pushNotificationsDeliver1") ||
+           (u.pathname === "/pushNotificationsDeliver2") ||
+           (u.pathname === "/pushNotificationsDeliver3")) {
+    res.writeHead(410, "GONE");
+    res.end("");
+    return;
+  }
+
   res.setHeader('Content-Type', 'text/html');
   if (req.httpVersionMajor != 2) {
     res.setHeader('Connection', 'close');
   }
   res.writeHead(200);
   res.end(content);
 }