<!DOCTYPE HTML>
<html>
<!--
Bug 1247685: Implement `applicationServerKey` for subscription association.

Any copyright is dedicated to the Public Domain.
http://creativecommons.org/licenses/publicdomain/

-->
<head>
  <title>Test for Bug 1247685</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247685">Mozilla Bug 1247685</a>
<p id="display"></p>
<div id="content" style="display: none">

</div>
<pre id="test">
</pre>

<script class="testbody" type="text/javascript">
  var isTestingMismatchedKey = false;
  var subscriptions = 0;
  var testKey; // Generated in `start`.

  function generateKey() {
    return crypto.subtle.generateKey({
      name: "ECDSA",
      namedCurve: "P-256",
    }, true, ["sign", "verify"]).then(cryptoKey =>
      crypto.subtle.exportKey("raw", cryptoKey.publicKey)
    ).then(publicKey => new Uint8Array(publicKey));
  }

  var registration;
  add_task(async function start() {
    await setupPrefsAndReplaceService({
      register(pageRecord) {
        ok(pageRecord.appServerKey.length,
          "App server key should not be empty");
        if (pageRecord.appServerKey.length != 65) {
          // eslint-disable-next-line no-throw-literal
          throw { result:
            SpecialPowers.Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR };
          }
          if (isTestingMismatchedKey) {
          // eslint-disable-next-line no-throw-literal
          throw { result:
                  SpecialPowers.Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR };
        }
        return {
          endpoint: "https://example.com/push/" + (++subscriptions),
          appServerKey: pageRecord.appServerKey,
        };
      },

      registration(pageRecord) {
        return {
          endpoint: "https://example.com/push/subWithKey",
          appServerKey: testKey,
        };
      },
    });
    await setPushPermission(true);
    testKey = await generateKey();

    var url = "worker.js?" + (Math.random());
    registration = await navigator.serviceWorker.register(url, {scope: "."});
    await waitForActive(registration);
  });

  var controlledFrame;
  add_task(async function createControlledIFrame() {
    controlledFrame = await injectControlledFrame();
  });

  add_task(async function emptyKey() {
    try {
      await registration.pushManager.subscribe({
        applicationServerKey: new ArrayBuffer(0),
      });
      ok(false, "Should reject for empty app server keys");
    } catch (error) {
      ok(error instanceof DOMException,
        "Wrong exception type for empty key");
      is(error.name, "InvalidAccessError",
        "Wrong exception name for empty key");
    }
  });

  add_task(async function invalidKey() {
    try {
      await registration.pushManager.subscribe({
        applicationServerKey: new Uint8Array([0]),
      });
      ok(false, "Should reject for invalid app server keys");
    } catch (error) {
      ok(error instanceof DOMException,
        "Wrong exception type for invalid key");
      is(error.name, "InvalidAccessError",
        "Wrong exception name for invalid key");
    }
  });

  add_task(async function validKey() {
    var pushSubscription = await registration.pushManager.subscribe({
      applicationServerKey: await generateKey(),
    });
    is(pushSubscription.endpoint, "https://example.com/push/1",
      "Wrong endpoint for subscription with key");
    is(pushSubscription.options.applicationServerKey,
       pushSubscription.options.applicationServerKey,
       "App server key getter should return the same object");
  });

  add_task(async function retrieveKey() {
    var pushSubscription = await registration.pushManager.getSubscription();
    is(pushSubscription.endpoint, "https://example.com/push/subWithKey",
      "Got wrong endpoint for subscription with key");
    isDeeply(
      new Uint8Array(pushSubscription.options.applicationServerKey),
      testKey,
      "Got wrong app server key"
    );
  });

  add_task(async function mismatchedKey() {
    isTestingMismatchedKey = true;
    try {
      await registration.pushManager.subscribe({
        applicationServerKey: await generateKey(),
      });
      ok(false, "Should reject for mismatched app server keys");
    } catch (error) {
      ok(error instanceof DOMException,
        "Wrong exception type for mismatched key");
      is(error.name, "InvalidStateError",
        "Wrong exception name for mismatched key");
    } finally {
      isTestingMismatchedKey = false;
    }
  });

  add_task(async function emptyKeyInWorker() {
    var errorInfo = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: new ArrayBuffer(0),
    });
    ok(errorInfo.isDOMException,
      "Wrong exception type in worker for empty key");
    is(errorInfo.name, "InvalidAccessError",
      "Wrong exception name in worker for empty key");
  });

  add_task(async function invalidKeyInWorker() {
    var errorInfo = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: new Uint8Array([1]),
    });
    ok(errorInfo.isDOMException,
      "Wrong exception type in worker for invalid key");
    is(errorInfo.name, "InvalidAccessError",
      "Wrong exception name in worker for invalid key");
  });

  add_task(async function validKeyInWorker() {
    var key = await generateKey();
    var data = await sendRequestToWorker({
      type: "subscribeWithKey",
      key,
    });
    is(data.endpoint, "https://example.com/push/2",
      "Wrong endpoint for subscription with key created in worker");
    isDeeply(new Uint8Array(data.key), key,
      "Wrong app server key for subscription created in worker");
  });

  add_task(async function mismatchedKeyInWorker() {
    isTestingMismatchedKey = true;
    try {
      var errorInfo = await sendRequestToWorker({
        type: "subscribeWithKey",
        key: await generateKey(),
      });
      ok(errorInfo.isDOMException,
        "Wrong exception type in worker for mismatched key");
      is(errorInfo.name, "InvalidStateError",
        "Wrong exception name in worker for mismatched key");
    } finally {
      isTestingMismatchedKey = false;
    }
  });

  add_task(async function validKeyBuffer() {
    var key = await generateKey();
    var pushSubscription = await registration.pushManager.subscribe({
      applicationServerKey: key.buffer,
    });
    is(pushSubscription.endpoint, "https://example.com/push/3",
      "Wrong endpoint for subscription created with key buffer");
    var subscriptionKey = pushSubscription.options.applicationServerKey;
    isDeeply(new Uint8Array(subscriptionKey), key,
      "App server key getter should match given key");
  });

  add_task(async function validKeyBufferInWorker() {
    var key = await generateKey();
    var data = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: key.buffer,
    });
    is(data.endpoint, "https://example.com/push/4",
      "Wrong endpoint for subscription with key buffer created in worker");
    isDeeply(new Uint8Array(data.key), key,
      "App server key getter should match given key for subscription created in worker");
  });

  add_task(async function validKeyString() {
    var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
    var key = base64UrlDecode(base64Key);
    var pushSubscription = await registration.pushManager.subscribe({
      applicationServerKey: base64Key,
    });
    is(pushSubscription.endpoint, "https://example.com/push/5",
      "Wrong endpoint for subscription created with Base64-encoded key");
    isDeeply(new Uint8Array(pushSubscription.options.applicationServerKey), key,
      "App server key getter should match Base64-decoded key");
  });

  add_task(async function validKeyStringInWorker() {
    var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
    var key = base64UrlDecode(base64Key);
    var data = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: base64Key,
    });
    is(data.endpoint, "https://example.com/push/6",
      "Wrong endpoint for subscription created with Base64-encoded key in worker");
    isDeeply(new Uint8Array(data.key), key,
      "App server key getter should match decoded key for subscription created in worker");
  });

  add_task(async function invalidKeyString() {
    try {
      await registration.pushManager.subscribe({
        applicationServerKey: "!@#$^&*",
      });
      ok(false, "Should reject for invalid Base64-encoded keys");
    } catch (error) {
      ok(error instanceof DOMException,
        "Wrong exception type for invalid Base64-encoded key");
      is(error.name, "InvalidCharacterError",
        "Wrong exception name for invalid Base64-encoded key");
    }
  });

  add_task(async function invalidKeyStringInWorker() {
    var errorInfo = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: "!@#$^&*",
    });
    ok(errorInfo.isDOMException,
      "Wrong exception type in worker for invalid Base64-encoded key");
    is(errorInfo.name, "InvalidCharacterError",
      "Wrong exception name in worker for invalid Base64-encoded key");
  });

  add_task(async function emptyKeyString() {
    try {
      await registration.pushManager.subscribe({
        applicationServerKey: "",
      });
      ok(false, "Should reject for empty key strings");
    } catch (error) {
      ok(error instanceof DOMException,
        "Wrong exception type for empty key string");
      is(error.name, "InvalidAccessError",
        "Wrong exception name for empty key string");
    }
  });

  add_task(async function emptyKeyStringInWorker() {
    var errorInfo = await sendRequestToWorker({
      type: "subscribeWithKey",
      key: "",
    });
    ok(errorInfo.isDOMException,
      "Wrong exception type in worker for empty key string");
    is(errorInfo.name, "InvalidAccessError",
      "Wrong exception name in worker for empty key string");
  });

  add_task(async function unsubscribe() {
    is(subscriptions, 6, "Wrong subscription count");
    controlledFrame.remove();
  });

  add_task(async function unregister() {
    await registration.unregister();
  });

</script>
</body>
</html>
