summaryrefslogtreecommitdiffstats
path: root/toolkit/identity/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/identity/tests')
-rw-r--r--toolkit/identity/tests/chrome/.eslintrc.js7
-rw-r--r--toolkit/identity/tests/chrome/chrome.ini13
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content.html32
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content.sjs36
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_alert.html20
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_framed.html17
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_perms.html64
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_popup.html25
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_redirect.html2
-rw-r--r--toolkit/identity/tests/chrome/sandbox_content_redirect.html^headers^2
-rw-r--r--toolkit/identity/tests/chrome/test_sandbox.xul324
-rw-r--r--toolkit/identity/tests/unit/.eslintrc.js7
-rw-r--r--toolkit/identity/tests/unit/data/idp_1/.well-known/browserid5
-rw-r--r--toolkit/identity/tests/unit/data/idp_invalid_1/.well-known/browserid5
-rw-r--r--toolkit/identity/tests/unit/head_identity.js256
-rw-r--r--toolkit/identity/tests/unit/tail_identity.js8
-rw-r--r--toolkit/identity/tests/unit/test_authentication.js159
-rw-r--r--toolkit/identity/tests/unit/test_crypto_service.js122
-rw-r--r--toolkit/identity/tests/unit/test_firefox_accounts.js270
-rw-r--r--toolkit/identity/tests/unit/test_identity.js114
-rw-r--r--toolkit/identity/tests/unit/test_identity_utils.js46
-rw-r--r--toolkit/identity/tests/unit/test_jwcrypto.js281
-rw-r--r--toolkit/identity/tests/unit/test_load_modules.js20
-rw-r--r--toolkit/identity/tests/unit/test_log_utils.js74
-rw-r--r--toolkit/identity/tests/unit/test_minimalidentity.js223
-rw-r--r--toolkit/identity/tests/unit/test_observer_topics.js114
-rw-r--r--toolkit/identity/tests/unit/test_provisioning.js242
-rw-r--r--toolkit/identity/tests/unit/test_relying_party.js255
-rw-r--r--toolkit/identity/tests/unit/test_store.js64
-rw-r--r--toolkit/identity/tests/unit/test_well-known.js90
-rw-r--r--toolkit/identity/tests/unit/xpcshell.ini24
31 files changed, 2921 insertions, 0 deletions
diff --git a/toolkit/identity/tests/chrome/.eslintrc.js b/toolkit/identity/tests/chrome/.eslintrc.js
new file mode 100644
index 000000000..2c669d844
--- /dev/null
+++ b/toolkit/identity/tests/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/toolkit/identity/tests/chrome/chrome.ini b/toolkit/identity/tests/chrome/chrome.ini
new file mode 100644
index 000000000..ffaff0fb1
--- /dev/null
+++ b/toolkit/identity/tests/chrome/chrome.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+skip-if = buildapp == 'b2g' || os == 'android'
+support-files =
+ sandbox_content.html
+ sandbox_content.sjs
+ sandbox_content_alert.html
+ sandbox_content_framed.html
+ sandbox_content_perms.html
+ sandbox_content_popup.html
+ sandbox_content_redirect.html
+ sandbox_content_redirect.html^headers^
+
+[test_sandbox.xul]
diff --git a/toolkit/identity/tests/chrome/sandbox_content.html b/toolkit/identity/tests/chrome/sandbox_content.html
new file mode 100644
index 000000000..9a9b63ac2
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+<meta charset="utf-8">
+<title>Page testing blocked content in the Sandbox</title>
+
+<link rel="stylesheet" src="sandbox_content.sjs?text/css"/>
+
+<script src="sandbox_content.sjs?application/javascript"></script>
+
+</head>
+
+<body>
+
+<img src="sandbox_content.sjs?image/jpeg"/>
+
+<!-- media -->
+<video src="sandbox_content.sjs?video/webm" autoplay="true"></video>
+<audio src="sandbox_content.sjs?audio/ogg" autoplay="true"></audio>
+
+<!-- plugins -->
+<embed src="sandbox_content.sjs?application/x-test"/>
+<object data="sandbox_content.sjs?application/x-test"></object>
+<applet code="sandbox_content.sjs?application/x-java-applet"></applet>
+
+<iframe src="sandbox_content.sjs?text/html"></iframe>
+
+</body>
+
+</html>
diff --git a/toolkit/identity/tests/chrome/sandbox_content.sjs b/toolkit/identity/tests/chrome/sandbox_content.sjs
new file mode 100644
index 000000000..2f562f214
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content.sjs
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ let loadedStateKey = "sandbox_content_loaded";
+ switch(request.queryString) {
+ case "reset": {
+ setState(loadedStateKey, "");
+ response.write("reset");
+ break;
+ }
+ case "get_loaded": {
+ response.setHeader("Content-Type", "text/plain", false);
+ let loaded = getState(loadedStateKey);
+ if (loaded)
+ response.write(loaded);
+ else
+ response.write("NOTHING");
+ break;
+ }
+ default: {
+ let contentType = decodeURIComponent(request.queryString);
+ // set the Content-Type equal to the query string
+ response.setHeader("Content-Type", contentType, false);
+ // If any content is loaded, append it's content type in state
+ let loaded = getState(loadedStateKey);
+ if (loaded)
+ loaded += ",";
+ setState(loadedStateKey, loaded + contentType);
+ break;
+ }
+ }
+}
diff --git a/toolkit/identity/tests/chrome/sandbox_content_alert.html b/toolkit/identity/tests/chrome/sandbox_content_alert.html
new file mode 100644
index 000000000..f07308e84
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_alert.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+<meta charset="utf-8">
+<title>Page creating an alert inside the Sandbox</title>
+
+<script>
+
+alert("The user shouldn't see this");
+
+</script>
+
+</head>
+
+<body>
+
+</body>
+</html>
diff --git a/toolkit/identity/tests/chrome/sandbox_content_framed.html b/toolkit/identity/tests/chrome/sandbox_content_framed.html
new file mode 100644
index 000000000..72b0c49d0
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_framed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+<meta charset="utf-8">
+<title>Page testing blocked content in an iframe inside the Sandbox</title>
+
+</head>
+
+<body>
+
+<iframe src="sandbox_content.html"></iframe>
+
+</body>
+
+</html>
diff --git a/toolkit/identity/tests/chrome/sandbox_content_perms.html b/toolkit/identity/tests/chrome/sandbox_content_perms.html
new file mode 100644
index 000000000..d24c683f8
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_perms.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Page testing content in the Sandbox can't escape</title>
+ <script type="application/javascript;version=1.8">
+ const TEST_BASE = "http://mochi.test:8888/chrome/toolkit/identity/tests/chrome/"
+ const Ci = SpecialPowers.Ci;
+
+ function expectException(aFunc) {
+ try {
+ aFunc();
+ } catch (ex) {
+ return true;
+ }
+ return false;
+ }
+
+ function CcNotPresent() {
+ if (typeof Components === 'undefined')
+ return true;
+ // Components shim doesn't define Components.classes.
+ try {
+ return typeof Components.classes === 'undefined';
+ } catch (e) {
+ return false;
+ }
+ }
+
+ // Build an object with test results (true = pass)
+ let results = {
+ windowTop: window.top == window,
+
+ qiWindow: expectException(function() {
+ let isForced = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .docCharsetIsForced;
+ }),
+
+ ccAccess: !!CcNotPresent(),
+ };
+
+ let resultsJSON = JSON.stringify(results);
+
+ // Send the results to the mochitest server so the test file can retrieve them.
+ let stateURL = TEST_BASE + "sandbox_content.sjs"
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", stateURL + "?" + encodeURIComponent(resultsJSON), true);
+ xhr.onload = function() {
+ if (xhr.status != 200) {
+ dump("Failed sending results\n");
+ }
+ };
+ xhr.send();
+
+ </script>
+ </head>
+
+ <body>
+
+ </body>
+</html>
diff --git a/toolkit/identity/tests/chrome/sandbox_content_popup.html b/toolkit/identity/tests/chrome/sandbox_content_popup.html
new file mode 100644
index 000000000..cb21f706f
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_popup.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+<meta charset="utf-8">
+<title>Page creating an popup inside the Sandbox</title>
+
+<script>
+
+var strWindowFeatures = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
+
+var uri = "data:text/html,";
+uri += encodeURI("<body onload='setTimeout(window.close, 1000)'>");
+
+var win = window.open(uri, "sandbox_popup", strWindowFeatures);
+
+</script>
+
+</head>
+
+<body>
+
+</body>
+</html>
diff --git a/toolkit/identity/tests/chrome/sandbox_content_redirect.html b/toolkit/identity/tests/chrome/sandbox_content_redirect.html
new file mode 100644
index 000000000..7570ffad8
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_redirect.html
@@ -0,0 +1,2 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
diff --git a/toolkit/identity/tests/chrome/sandbox_content_redirect.html^headers^ b/toolkit/identity/tests/chrome/sandbox_content_redirect.html^headers^
new file mode 100644
index 000000000..7c06340b9
--- /dev/null
+++ b/toolkit/identity/tests/chrome/sandbox_content_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: http://mochi.test:8888/chrome/toolkit/identity/tests/chrome/sandbox_content.html
diff --git a/toolkit/identity/tests/chrome/test_sandbox.xul b/toolkit/identity/tests/chrome/test_sandbox.xul
new file mode 100644
index 000000000..2b353c53b
--- /dev/null
+++ b/toolkit/identity/tests/chrome/test_sandbox.xul
@@ -0,0 +1,324 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=762993
+-->
+<window title="Mozilla Bug 762993"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run_next_test();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=762993"
+ target="_blank">Mozilla Bug 762993</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript;version=1.8">
+ <![CDATA[
+
+ /** Test for Bug 762993 **/
+
+"use strict";
+
+SimpleTest.expectAssertions(1);
+
+SimpleTest.waitForExplicitFinish();
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+
+const TEST_URL_1 = "https://example.com/";
+// No trailing slash plus port to test normalization
+const TEST_URL_2 = "https://example.com:443";
+
+const TEST_BASE = "http://mochi.test:8888/chrome/toolkit/identity/tests/chrome/"
+const STATE_URL = TEST_BASE + "sandbox_content.sjs"
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+Services.prefs.setBoolPref("toolkit.identity.debug", true);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Sandbox",
+ "resource://gre/modules/identity/Sandbox.jsm");
+
+function check_sandbox(aSandbox, aURL) {
+ ok(aSandbox.id > 0, "valid ID");
+ is(aSandbox._url, aURL, "matching URL (with normalization)");
+ isnot(aSandbox._frame, null, "frame");
+ isnot(aSandbox._container, null, "container");
+ let docPrincipal = aSandbox._frame.contentDocument.nodePrincipal;
+ is(secMan.isSystemPrincipal(docPrincipal), false,
+ "principal must not be system");
+}
+
+/**
+ * Free the sandbox and make sure all properties that are not booleans,
+ * functions or numbers were freed.
+ */
+function free_and_check_sandbox(aSandbox) {
+ SimpleTest.executeSoon(function() {
+ aSandbox.free();
+
+ for(let prop in aSandbox) {
+ // Don't trigger the "id" getter when the frame is supposed to be freed already
+ if (prop == "id")
+ continue;
+ let propType = typeof(aSandbox[prop]);
+ if (propType == "boolean" || propType == "function" || propType == "number")
+ continue;
+ is(aSandbox[prop], null, "freed " + prop);
+ }
+ run_next_test();
+ });
+}
+
+function reset_server_state() {
+ // Now reset the server state
+ let resetReq = new XMLHttpRequest();
+ resetReq.open("GET", STATE_URL + "?reset", false);
+ resetReq.send();
+}
+
+function test_creation() {
+ new Sandbox(TEST_URL_1, function sandboxCB(aSandbox) {
+ check_sandbox(aSandbox, TEST_URL_1);
+ free_and_check_sandbox(aSandbox);
+ });
+}
+
+function test_reload() {
+ new Sandbox(TEST_URL_1, function sandboxCB(aSandbox) {
+ check_sandbox(aSandbox, TEST_URL_1);
+ let originalId = aSandbox.id;
+
+ aSandbox.reload(function sandboxReloadCB(aSandbox) {
+ check_sandbox(aSandbox, TEST_URL_1);
+ is(aSandbox.id, originalId, "Sandbox ID should be the same after reload");
+ free_and_check_sandbox(aSandbox);
+ });
+ });
+}
+
+function test_url_normalization() {
+ new Sandbox(TEST_URL_2, function sandboxCB(aSandbox) {
+ // TEST_URL_2 should be normalized into the form of TEST_URL_1
+ check_sandbox(aSandbox, TEST_URL_1);
+ free_and_check_sandbox(aSandbox);
+ });
+}
+
+/**
+ * Check with the server's state to see what content was loaded then reset it.
+ */
+function check_loaded_content(aSandbox, aNothingShouldLoad, aCallback) {
+
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", STATE_URL + "?get_loaded", true);
+ xhr.onload = function() {
+ let res = xhr.responseText;
+ is(xhr.status, 200, "Check successful response");
+
+ if (aNothingShouldLoad) {
+ is(res, "NOTHING", "Check that nothing was loaded on the server");
+ } else {
+ let allowedTypes = [ "application/javascript", "text/html", "application/x-test" ];
+ let loadedTypes = res == "NOTHING" ? [] : res.split(",");
+
+ for (let loadedType of loadedTypes) {
+ isnot(allowedTypes.indexOf(loadedType), -1, "Check that " + loadedType + " was expected to load"); // TODO
+ }
+
+ isnot(loadedTypes.indexOf("application/javascript"), -1, "Check JS was loaded");
+ isnot(loadedTypes.indexOf("text/html"), -1, "Check iframe was loaded");
+ is(loadedTypes.indexOf("video/webm"), -1, "Check webm was not loaded");
+ is(loadedTypes.indexOf("audio/ogg"), -1, "Check ogg was not loaded");
+
+ // Check that no plugin tags have a type other than TYPE_NULL (failed load)
+ // --
+ // Checking if a channel was opened is not sufficient for plugin tags --
+ // An object tag may still be allowed to load a sub-document, but not a
+ // plugin, so it will open a channel but then abort when it gets a
+ // plugin-type.
+ let doc = aSandbox._frame.contentDocument;
+ let nullType = Components.interfaces.nsIObjectLoadingContent.TYPE_NULL;
+ for (let tag of doc.querySelectorAll("embed, object, applet")) {
+ tag instanceof Components.interfaces.nsIObjectLoadingContent;
+ is(tag.displayedType, nullType, "Check that plugin did not load content");
+ }
+ }
+
+ reset_server_state();
+
+ aCallback();
+ };
+ xhr.send();
+}
+
+/**
+ * Helper to check that only certain content is loaded on creation and during reload.
+ */
+function check_disabled_content(aSandboxURL, aNothingShouldLoad = false) {
+ new Sandbox(aSandboxURL, function sandboxCB(aSandbox) {
+ check_sandbox(aSandbox, aSandboxURL);
+ let originalId = aSandbox.id;
+
+ setTimeout(function() {
+ check_loaded_content(aSandbox, aNothingShouldLoad, function checkFinished() {
+
+ info("reload the sandbox content");
+ aSandbox.reload(function sandboxReloadCB(aSandbox) {
+ check_sandbox(aSandbox, aSandboxURL);
+ is(aSandbox.id, originalId, "Sandbox ID should be the same after reload");
+
+ setTimeout(function() {
+ check_loaded_content(aSandbox, aNothingShouldLoad, function reloadCheckFinished() {
+ free_and_check_sandbox(aSandbox);
+ });
+ }, 5000);
+ });
+ });
+ }, 5000);
+ });
+}
+
+function test_disabled_content() {
+ let url = TEST_BASE + "sandbox_content.html";
+ check_disabled_content(url);
+}
+
+// Same as test above but with content in an iframe.
+function test_disabled_content_framed() {
+ let url = TEST_BASE + "sandbox_content_framed.html";
+ check_disabled_content(url);
+}
+
+function test_redirect() {
+ let url = TEST_BASE + "sandbox_content_redirect.html";
+ check_disabled_content(url);
+}
+
+function WindowObserver(aCallback) {
+ this.observe = function(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+ Services.ww.unregisterNotification(this);
+
+ let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ ok(!domWin, "No window should be opened");
+ SimpleTest.executeSoon(function() {
+ info("Closing opened window");
+ domWin.close();
+ aCallback();
+ });
+ }
+}
+
+// Can the sandbox call window.alert() or popup other UI?
+function test_alert() {
+ let alertURL = TEST_BASE + "sandbox_content_alert.html";
+
+ new Sandbox(alertURL, function sandboxCB(aSandbox) {
+ check_sandbox(aSandbox, alertURL);
+ setTimeout(function() {
+
+ let win = Services.wm.getMostRecentWindow(null);
+ isnot(win.document.documentElement.getAttribute("id"), "commonDialog",
+ "Make sure most recent window is not a dialog");
+ if (win.document.documentElement.getAttribute("id") == "commonDialog") {
+ // If a dialog did open, close it so we don't interfere with future tests
+ win.close()
+ }
+
+ free_and_check_sandbox(aSandbox);
+ }, 1000);
+ });
+}
+
+// Can the sandboxed page open a popup with window.open?
+function test_popup() {
+ let alertURL = TEST_BASE + "sandbox_content_popup.html";
+ let theSandbox;
+ function continueTest() {
+ // avoid double-free
+ if (!theSandbox)
+ return;
+ free_and_check_sandbox(theSandbox);
+ theSandbox = null;
+ }
+ let winObs = new WindowObserver(continueTest);
+ Services.ww.registerNotification(winObs);
+ new Sandbox(alertURL, function sandboxCB(aSandbox) {
+ theSandbox = aSandbox;
+ check_sandbox(aSandbox, alertURL);
+ // Wait 5 seconds to see if the window is going to open.
+ setTimeout(function() {
+ Services.ww.unregisterNotification(winObs);
+ continueTest();
+ }, 5000);
+ });
+}
+
+// Loading a page with a bad cert
+function test_bad_cert() {
+ let url = TEST_BASE + "sandbox_content.sjs?text/html";
+ url = url.replace("http://mochi.test:8888", "https://untrusted.example.com");
+ check_disabled_content(url, /*nothingShouldLoad=*/true);
+}
+
+// Loading a page to check window.top and other permissions.
+function test_frame_perms() {
+ let url = TEST_BASE + "sandbox_content_perms.html";
+ new Sandbox(url, function sandboxCB(aSandbox) {
+ check_sandbox(aSandbox, url);
+
+ // Give the content time to load
+ setTimeout(function() {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", STATE_URL + "?get_loaded", true);
+ xhr.responseType = "json";
+ xhr.onload = function() {
+ is(xhr.status, 200, "Check successful response");
+ is(typeof(xhr.response), "object", "Check response is object");
+ is(Object.keys(xhr.response).length, 3, "Check the number of perm. tests");
+ for (let test in xhr.response) {
+ ok(xhr.response[test], "Check result of " + test);
+ }
+
+ reset_server_state();
+ free_and_check_sandbox(aSandbox);
+ };
+ xhr.send();
+ }, 3000);
+ });
+}
+
+let TESTS = [test_creation, test_reload, test_url_normalization];
+TESTS.push(test_disabled_content, test_disabled_content_framed);
+TESTS.push(test_alert, test_popup, test_bad_cert);
+TESTS.push(test_redirect, test_frame_perms);
+
+function run_next_test() {
+ if (TESTS.length) {
+ let test = TESTS.shift();
+ info(test.name);
+ test();
+ } else {
+ Services.prefs.clearUserPref("toolkit.identity.debug");
+ SimpleTest.finish();
+ }
+}
+
+ ]]>
+ </script>
+</window>
diff --git a/toolkit/identity/tests/unit/.eslintrc.js b/toolkit/identity/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..fee088c17
--- /dev/null
+++ b/toolkit/identity/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/identity/tests/unit/data/idp_1/.well-known/browserid b/toolkit/identity/tests/unit/data/idp_1/.well-known/browserid
new file mode 100644
index 000000000..c7390457d
--- /dev/null
+++ b/toolkit/identity/tests/unit/data/idp_1/.well-known/browserid
@@ -0,0 +1,5 @@
+{
+ "public-key": {"algorithm":"RS","n":"65718905405105134410187227495885391609221288015566078542117409373192106382993306537273677557482085204736975067567111831005921322991127165013340443563713385983456311886801211241492470711576322130577278575529202840052753612576061450560588102139907846854501252327551303482213505265853706269864950437458242988327","e":"65537"},
+ "authentication": "/browserid/sign_in.html",
+ "provisioning": "/browserid/provision.html"
+}
diff --git a/toolkit/identity/tests/unit/data/idp_invalid_1/.well-known/browserid b/toolkit/identity/tests/unit/data/idp_invalid_1/.well-known/browserid
new file mode 100644
index 000000000..6bcd9de91
--- /dev/null
+++ b/toolkit/identity/tests/unit/data/idp_invalid_1/.well-known/browserid
@@ -0,0 +1,5 @@
+{
+ "public-key": {"algorithm":"RS","n":"65718905405105134410187227495885391609221288015566078542117409373192106382993306537273677557482085204736975067567111831005921322991127165013340443563713385983456311886801211241492470711576322130577278575529202840052753612576061450560588102139907846854501252327551303482213505265853706269864950437458242988327","e":"65537"},
+ "authentication": "/browserid/sign_in.html",
+ // missing "provisioning"
+}
diff --git a/toolkit/identity/tests/unit/head_identity.js b/toolkit/identity/tests/unit/head_identity.js
new file mode 100644
index 000000000..a266e7aee
--- /dev/null
+++ b/toolkit/identity/tests/unit/head_identity.js
@@ -0,0 +1,256 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+
+// XXX until bug 937114 is fixed
+Cu.importGlobalProperties(["atob"]);
+
+// The following boilerplate makes sure that XPCOM calls
+// that use the profile directory work.
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
+ "resource://gre/modules/identity/jwcrypto.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "IdentityStore",
+ "resource://gre/modules/identity/IdentityStore.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Logger",
+ "resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "uuidGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const TEST_MESSAGE_MANAGER = "Mr McFeeley";
+const TEST_URL = "https://myfavoritebacon.com";
+const TEST_URL2 = "https://myfavoritebaconinacan.com";
+const TEST_USER = "user@mozilla.com";
+const TEST_PRIVKEY = "fake-privkey";
+const TEST_CERT = "fake-cert";
+const TEST_ASSERTION = "fake-assertion";
+const TEST_IDPPARAMS = {
+ domain: "myfavoriteflan.com",
+ authentication: "/foo/authenticate.html",
+ provisioning: "/foo/provision.html"
+};
+
+// The following are utility functions for Identity testing
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
+}
+
+function get_idstore() {
+ return IdentityStore;
+}
+
+function partial(fn) {
+ let args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
+ };
+}
+
+function uuid() {
+ return uuidGenerator.generateUUID().toString();
+}
+
+function base64UrlDecode(s) {
+ s = s.replace(/-/g, "+");
+ s = s.replace(/_/g, "/");
+
+ // Replace padding if it was stripped by the sender.
+ // See http://tools.ietf.org/html/rfc4648#section-4
+ switch (s.length % 4) {
+ case 0:
+ break; // No pad chars in this case
+ case 2:
+ s += "==";
+ break; // Two pad chars
+ case 3:
+ s += "=";
+ break; // One pad char
+ default:
+ throw new InputException("Illegal base64url string!");
+ }
+
+ // With correct padding restored, apply the standard base64 decoder
+ return atob(s);
+}
+
+// create a mock "doc" object, which the Identity Service
+// uses as a pointer back into the doc object
+function mock_doc(aIdentity, aOrigin, aDoFunc) {
+ let mockedDoc = {};
+ mockedDoc.id = uuid();
+ mockedDoc.loggedInUser = aIdentity;
+ mockedDoc.origin = aOrigin;
+ mockedDoc["do"] = aDoFunc;
+ mockedDoc._mm = TEST_MESSAGE_MANAGER;
+ mockedDoc.doReady = partial(aDoFunc, "ready");
+ mockedDoc.doLogin = partial(aDoFunc, "login");
+ mockedDoc.doLogout = partial(aDoFunc, "logout");
+ mockedDoc.doError = partial(aDoFunc, "error");
+ mockedDoc.doCancel = partial(aDoFunc, "cancel");
+ mockedDoc.doCoffee = partial(aDoFunc, "coffee");
+ mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown");
+
+ mockedDoc.RP = mockedDoc;
+
+ return mockedDoc;
+}
+
+function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) {
+ let mockedDoc = {};
+ mockedDoc.id = uuid();
+ mockedDoc.emailHint = aIdentity;
+ mockedDoc.origin = aOrigin;
+ mockedDoc.wantIssuer = "firefox-accounts";
+ mockedDoc._mm = TEST_MESSAGE_MANAGER;
+
+ mockedDoc.doReady = partial(aDoFunc, "ready");
+ mockedDoc.doLogin = partial(aDoFunc, "login");
+ mockedDoc.doLogout = partial(aDoFunc, "logout");
+ mockedDoc.doError = partial(aDoFunc, "error");
+ mockedDoc.doCancel = partial(aDoFunc, "cancel");
+ mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown");
+
+ mockedDoc.RP = mockedDoc;
+
+ return mockedDoc;
+}
+
+// mimicking callback funtionality for ease of testing
+// this observer auto-removes itself after the observe function
+// is called, so this is meant to observe only ONE event.
+function makeObserver(aObserveTopic, aObserveFunc) {
+ let observer = {
+ // nsISupports provides type management in C++
+ // nsIObserver is to be an observer
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic == aObserveTopic) {
+ aObserveFunc(aSubject, aTopic, aData);
+ Services.obs.removeObserver(observer, aObserveTopic);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, aObserveTopic, false);
+}
+
+// set up the ID service with an identity with keypair and all
+// when ready, invoke callback with the identity
+function setup_test_identity(identity, cert, cb) {
+ // set up the store so that we're supposed to be logged in
+ let store = get_idstore();
+
+ function keyGenerated(err, kpo) {
+ store.addIdentity(identity, kpo, cert);
+ cb();
+ }
+
+ jwcrypto.generateKeyPair("DS160", keyGenerated);
+}
+
+// takes a list of functions and returns a function that
+// when called the first time, calls the first func,
+// then the next time the second, etc.
+function call_sequentially() {
+ let numCalls = 0;
+ let funcs = arguments;
+
+ return function() {
+ if (!funcs[numCalls]) {
+ let argString = Array.prototype.slice.call(arguments).join(",");
+ do_throw("Too many calls: " + argString);
+ return;
+ }
+ funcs[numCalls].apply(funcs[numCalls], arguments);
+ numCalls += 1;
+ };
+}
+
+/*
+ * Setup a provisioning workflow with appropriate callbacks
+ *
+ * identity is the email we're provisioning.
+ *
+ * afterSetupCallback is required.
+ *
+ * doneProvisioningCallback is optional, if the caller
+ * wants to be notified when the whole provisioning workflow is done
+ *
+ * frameCallbacks is optional, contains the callbacks that the sandbox
+ * frame would provide in response to DOM calls.
+ */
+function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallback, callerCallbacks) {
+ IDService.reset();
+
+ let provId = uuid();
+ IDService.IDP._provisionFlows[provId] = {
+ identity : identity,
+ idpParams: TEST_IDPPARAMS,
+ callback: function(err) {
+ if (doneProvisioningCallback)
+ doneProvisioningCallback(err);
+ },
+ sandbox: {
+ // Emulate the free() method on the iframe sandbox
+ free: function() {}
+ }
+ };
+
+ let caller = {};
+ caller.id = provId;
+ caller.doBeginProvisioningCallback = function(id, duration_s) {
+ if (callerCallbacks && callerCallbacks.beginProvisioningCallback)
+ callerCallbacks.beginProvisioningCallback(id, duration_s);
+ };
+ caller.doGenKeyPairCallback = function(pk) {
+ if (callerCallbacks && callerCallbacks.genKeyPairCallback)
+ callerCallbacks.genKeyPairCallback(pk);
+ };
+
+ afterSetupCallback(caller);
+}
+
+// Switch debug messages on by default
+var initialPrefDebugValue = false;
+try {
+ initialPrefDebugValue = Services.prefs.getBoolPref("toolkit.identity.debug");
+} catch (noPref) {}
+Services.prefs.setBoolPref("toolkit.identity.debug", true);
+
+// Switch on firefox accounts
+var initialPrefFXAValue = false;
+try {
+ initialPrefFXAValue = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
+} catch (noPref) {}
+Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
+
+// after execution, restore prefs
+do_register_cleanup(function() {
+ log("restoring prefs to their initial values");
+ Services.prefs.setBoolPref("toolkit.identity.debug", initialPrefDebugValue);
+ Services.prefs.setBoolPref("identity.fxaccounts.enabled", initialPrefFXAValue);
+});
+
+
diff --git a/toolkit/identity/tests/unit/tail_identity.js b/toolkit/identity/tests/unit/tail_identity.js
new file mode 100644
index 000000000..c263f8369
--- /dev/null
+++ b/toolkit/identity/tests/unit/tail_identity.js
@@ -0,0 +1,8 @@
+
+// pre-emptively shut down to clear resources
+if (typeof IdentityService !== "undefined") {
+ IdentityService.shutdown();
+} else if (typeof IDService !== "undefined") {
+ IDService.shutdown();
+}
+
diff --git a/toolkit/identity/tests/unit/test_authentication.js b/toolkit/identity/tests/unit/test_authentication.js
new file mode 100644
index 000000000..3f24e2e37
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_authentication.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
+ "resource://gre/modules/identity/jwcrypto.jsm");
+
+function test_begin_authentication_flow() {
+ do_test_pending();
+ let _provId = null;
+
+ // set up a watch, to be consistent
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {});
+ IDService.RP.watch(mockedDoc);
+
+ // The identity-auth notification is sent up to the UX from the
+ // _doAuthentication function. Be ready to receive it and call
+ // beginAuthentication
+ makeObserver("identity-auth", function (aSubject, aTopic, aData) {
+ do_check_neq(aSubject, null);
+
+ do_check_eq(aSubject.wrappedJSObject.provId, _provId);
+
+ do_test_finished();
+ run_next_test();
+ });
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ _provId = caller.id;
+ IDService.IDP.beginProvisioning(caller);
+ }, function() {},
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+
+ // let's say this user needs to authenticate
+ IDService.IDP._doAuthentication(_provId, {idpParams:TEST_IDPPARAMS});
+ }
+ }
+ );
+}
+
+function test_complete_authentication_flow() {
+ do_test_pending();
+ let _provId = null;
+ let _authId = null;
+ let id = TEST_USER;
+
+ let callbacksFired = false;
+ let loginStateChanged = false;
+ let identityAuthComplete = false;
+
+ // The result of authentication should be a successful login
+ IDService.reset();
+
+ setup_test_identity(id, TEST_CERT, function() {
+ // set it up so we're supposed to be logged in to TEST_URL
+
+ get_idstore().setLoginState(TEST_URL, true, id);
+
+ // When we authenticate, our ready callback will be fired.
+ // At the same time, a separate topic will be sent up to the
+ // the observer in the UI. The test is complete when both
+ // events have occurred.
+ let mockedDoc = mock_doc(id, TEST_URL, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+
+ // if notification already received by observer, test is done
+ callbacksFired = true;
+ if (loginStateChanged && identityAuthComplete) {
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ ));
+
+ makeObserver("identity-auth-complete", function(aSubject, aTopic, aData) {
+ identityAuthComplete = true;
+ do_test_finished();
+ run_next_test();
+ });
+
+ makeObserver("identity-login-state-changed", function (aSubject, aTopic, aData) {
+ do_check_neq(aSubject, null);
+
+ do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+ do_check_eq(aData, id);
+
+ // if callbacks in caller doc already fired, test is done.
+ loginStateChanged = true;
+ if (callbacksFired && identityAuthComplete) {
+ do_test_finished();
+ run_next_test();
+ }
+ });
+
+ IDService.RP.watch(mockedDoc);
+
+ // Create a provisioning flow for our auth flow to attach to
+ setup_provisioning(
+ TEST_USER,
+ function(provFlow) {
+ _provId = provFlow.id;
+
+ IDService.IDP.beginProvisioning(provFlow);
+ }, function() {},
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ // let's say this user needs to authenticate
+ IDService.IDP._doAuthentication(_provId, {idpParams:TEST_IDPPARAMS});
+
+ // test_begin_authentication_flow verifies that the right
+ // message is sent to the UI. So that works. Moving on,
+ // the UI calls setAuthenticationFlow ...
+ _authId = uuid();
+ IDService.IDP.setAuthenticationFlow(_authId, _provId);
+
+ // ... then the UI calls beginAuthentication ...
+ authCaller.id = _authId;
+ IDService.IDP._provisionFlows[_provId].caller = authCaller;
+ IDService.IDP.beginAuthentication(authCaller);
+ }
+ }
+ );
+ });
+
+ // A mock calling context
+ let authCaller = {
+ doBeginAuthenticationCallback: function doBeginAuthenticationCallback(identity) {
+ do_check_eq(identity, TEST_USER);
+ // completeAuthentication will emit "identity-auth-complete"
+ IDService.IDP.completeAuthentication(_authId);
+ },
+
+ doError: function(err) {
+ log("OW! My doError callback hurts!", err);
+ },
+ };
+
+}
+
+var TESTS = [];
+
+TESTS.push(test_begin_authentication_flow);
+TESTS.push(test_complete_authentication_flow);
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_crypto_service.js b/toolkit/identity/tests/unit/test_crypto_service.js
new file mode 100644
index 000000000..561c3804a
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_crypto_service.js
@@ -0,0 +1,122 @@
+/* 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://gre/modules/XPCOMUtils.jsm");
+Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+
+const idService = Cc["@mozilla.org/identity/crypto-service;1"]
+ .getService(Ci.nsIIdentityCryptoService);
+
+const ALG_DSA = "DS160";
+const ALG_RSA = "RS256";
+
+const BASE64_URL_ENCODINGS = [
+ // The vectors from RFC 4648 are very silly, but we may as well include them.
+ ["", ""],
+ ["f", "Zg=="],
+ ["fo", "Zm8="],
+ ["foo", "Zm9v"],
+ ["foob", "Zm9vYg=="],
+ ["fooba", "Zm9vYmE="],
+ ["foobar", "Zm9vYmFy"],
+
+ // It's quite likely you could get a string like this in an assertion audience
+ ["i-like-pie.com", "aS1saWtlLXBpZS5jb20="],
+
+ // A few extra to be really sure
+ ["andré@example.com", "YW5kcsOpQGV4YW1wbGUuY29t"],
+ ["πόλλ' οἶδ' ἀλώπηξ, ἀλλ' ἐχῖνος ἓν μέγα",
+ "z4DPjM67zrsnIM6_4by2zrQnIOG8gM67z47PgM63zr4sIOG8gM67zrsnIOG8kM-H4b-Wzr3Ov8-CIOG8k869IM68zq3Os86x"],
+];
+
+// When the output of an operation is a
+function do_check_eq_or_slightly_less(x, y) {
+ do_check_true(x >= y - (3 * 8));
+}
+
+function test_base64_roundtrip() {
+ let message = "Attack at dawn!";
+ let encoded = idService.base64UrlEncode(message);
+ let decoded = base64UrlDecode(encoded);
+ do_check_neq(message, encoded);
+ do_check_eq(decoded, message);
+ run_next_test();
+}
+
+function test_dsa() {
+ idService.generateKeyPair(ALG_DSA, function (rv, keyPair) {
+ log("DSA generateKeyPair finished ", rv);
+ do_check_true(Components.isSuccessCode(rv));
+ do_check_eq(typeof keyPair.sign, "function");
+ do_check_eq(keyPair.keyType, ALG_DSA);
+ do_check_eq_or_slightly_less(keyPair.hexDSAGenerator.length, 1024 / 8 * 2);
+ do_check_eq_or_slightly_less(keyPair.hexDSAPrime.length, 1024 / 8 * 2);
+ do_check_eq_or_slightly_less(keyPair.hexDSASubPrime.length, 160 / 8 * 2);
+ do_check_eq_or_slightly_less(keyPair.hexDSAPublicValue.length, 1024 / 8 * 2);
+ // XXX: test that RSA parameters throw the correct error
+
+ log("about to sign with DSA key");
+ keyPair.sign("foo", function (rv2, signature) {
+ log("DSA sign finished ", rv2, signature);
+ do_check_true(Components.isSuccessCode(rv2));
+ do_check_true(signature.length > 1);
+ // TODO: verify the signature with the public key
+ run_next_test();
+ });
+ });
+}
+
+function test_rsa() {
+ idService.generateKeyPair(ALG_RSA, function (rv, keyPair) {
+ log("RSA generateKeyPair finished ", rv);
+ do_check_true(Components.isSuccessCode(rv));
+ do_check_eq(typeof keyPair.sign, "function");
+ do_check_eq(keyPair.keyType, ALG_RSA);
+ do_check_eq_or_slightly_less(keyPair.hexRSAPublicKeyModulus.length,
+ 2048 / 8);
+ do_check_true(keyPair.hexRSAPublicKeyExponent.length > 1);
+
+ log("about to sign with RSA key");
+ keyPair.sign("foo", function (rv2, signature) {
+ log("RSA sign finished ", rv2, signature);
+ do_check_true(Components.isSuccessCode(rv2));
+ do_check_true(signature.length > 1);
+ run_next_test();
+ });
+ });
+}
+
+function test_base64UrlEncode() {
+ for (let [source, target] of BASE64_URL_ENCODINGS) {
+ do_check_eq(target, idService.base64UrlEncode(source));
+ }
+ run_next_test();
+}
+
+function test_base64UrlDecode() {
+ let utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ utf8Converter.charset = "UTF-8";
+
+ // We know the encoding of our inputs - on conversion back out again, make
+ // sure they're the same.
+ for (let [source, target] of BASE64_URL_ENCODINGS) {
+ let result = utf8Converter.ConvertToUnicode(base64UrlDecode(target));
+ result += utf8Converter.Finish();
+ do_check_eq(source, result);
+ }
+ run_next_test();
+}
+
+add_test(test_base64_roundtrip);
+add_test(test_dsa);
+add_test(test_rsa);
+add_test(test_base64UrlEncode);
+add_test(test_base64UrlDecode);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_firefox_accounts.js b/toolkit/identity/tests/unit/test_firefox_accounts.js
new file mode 100644
index 000000000..c0c63deb6
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_firefox_accounts.js
@@ -0,0 +1,270 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/DOMIdentity.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
+ "resource://gre/modules/identity/FirefoxAccounts.jsm");
+
+// Make the profile dir available; this is necessary so that
+// services/fxaccounts/FxAccounts.jsm can read and write its signed-in user
+// data.
+do_get_profile();
+
+function MockFXAManager() {
+ this.signedInUser = true;
+}
+MockFXAManager.prototype = {
+ getAssertion: function(audience) {
+ let result = this.signedInUser ? TEST_ASSERTION : null;
+ return Promise.resolve(result);
+ },
+
+ signOut: function() {
+ this.signedInUser = false;
+ return Promise.resolve(null);
+ },
+
+ signIn: function(user) {
+ this.signedInUser = user;
+ return Promise.resolve(user);
+ },
+}
+
+var originalManager = FirefoxAccounts.fxAccountsManager;
+FirefoxAccounts.fxAccountsManager = new MockFXAManager();
+do_register_cleanup(() => {
+ log("restoring fxaccountsmanager");
+ FirefoxAccounts.fxAccountsManager = originalManager;
+});
+
+function withNobodySignedIn() {
+ return FirefoxAccounts.fxAccountsManager.signOut();
+}
+
+function withSomebodySignedIn() {
+ return FirefoxAccounts.fxAccountsManager.signIn('Pertelote');
+}
+
+function test_overall() {
+ do_check_neq(FirefoxAccounts, null);
+ run_next_test();
+}
+
+function test_mock() {
+ do_test_pending();
+
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.fxAccountsManager.getAssertion().then(assertion => {
+ do_check_eq(assertion, TEST_ASSERTION);
+ do_test_finished();
+ run_next_test();
+ });
+ });
+}
+
+function test_watch_signed_in() {
+ do_test_pending();
+
+ let received = [];
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) {
+ received.push([method, data]);
+
+ if (method == "ready") {
+ // confirm that we were signed in and then ready was called
+ do_check_eq(received.length, 2);
+ do_check_eq(received[0][0], "login");
+ do_check_eq(received[0][1], TEST_ASSERTION);
+ do_check_eq(received[1][0], "ready");
+ do_test_finished();
+ run_next_test();
+ }
+ });
+
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+}
+
+function test_watch_signed_out() {
+ do_test_pending();
+
+ let received = [];
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
+ received.push(method);
+
+ if (method == "ready") {
+ // confirm that we were signed out and then ready was called
+ do_check_eq(received.length, 2);
+ do_check_eq(received[0], "logout");
+ do_check_eq(received[1], "ready");
+
+ do_test_finished();
+ run_next_test();
+ }
+ });
+
+ withNobodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+}
+
+function test_request() {
+ do_test_pending();
+
+ let received = [];
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) {
+ received.push([method, data]);
+
+ // On watch(), we are signed out. Then we call request().
+ if (received.length === 2) {
+ do_check_eq(received[0][0], "logout");
+ do_check_eq(received[1][0], "ready");
+
+ // Pretend request() showed ux and the user signed in
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.RP.request(mockedRP.id);
+ });
+ }
+
+ if (received.length === 3) {
+ do_check_eq(received[2][0], "login");
+ do_check_eq(received[2][1], TEST_ASSERTION);
+
+ do_test_finished();
+ run_next_test();
+ }
+ });
+
+ // First, call watch() with nobody signed in
+ withNobodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+}
+
+function test_logout() {
+ do_test_pending();
+
+ let received = [];
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
+ received.push(method);
+
+ // At first, watch() signs us in automatically. Then we sign out.
+ if (received.length === 2) {
+ do_check_eq(received[0], "login");
+ do_check_eq(received[1], "ready");
+
+ FirefoxAccounts.RP.logout(mockedRP.id);
+ }
+
+ if (received.length === 3) {
+ do_check_eq(received[2], "logout");
+ do_test_finished();
+ run_next_test();
+ }
+ });
+
+ // First, call watch()
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+}
+
+function test_error() {
+ do_test_pending();
+
+ let received = [];
+
+ // Mock the fxAccountsManager so that getAssertion rejects its promise and
+ // triggers our onerror handler. (This is the method that's used internally
+ // by FirefoxAccounts.RP.request().)
+ let originalGetAssertion = FirefoxAccounts.fxAccountsManager.getAssertion;
+ FirefoxAccounts.fxAccountsManager.getAssertion = function(audience) {
+ return Promise.reject(new Error("barf!"));
+ };
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, message) {
+ // We will immediately receive an error, due to watch()'s attempt
+ // to getAssertion().
+ do_check_eq(method, "error");
+ do_check_true(/barf/.test(message));
+
+ // Put things back the way they were
+ FirefoxAccounts.fxAccountsManager.getAssertion = originalGetAssertion;
+
+ do_test_finished();
+ run_next_test();
+ });
+
+ // First, call watch()
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+}
+
+function test_child_process_shutdown() {
+ do_test_pending();
+ let rpCount = FirefoxAccounts.RP._rpFlows.size;
+
+ makeObserver("identity-child-process-shutdown", (aTopic, aSubject, aData) => {
+ // Last of all, the shutdown observer message will be fired.
+ // This takes place after the RP has a chance to delete flows
+ // and clean up.
+ do_check_eq(FirefoxAccounts.RP._rpFlows.size, rpCount);
+ do_test_finished();
+ run_next_test();
+ });
+
+ let mockedRP = mock_fxa_rp(null, TEST_URL, (method) => {
+ // We should enter this function for 'ready' and 'child-process-shutdown'.
+ // After we have a chance to do our thing, the shutdown observer message
+ // will fire and be caught by the function above.
+ do_check_eq(FirefoxAccounts.RP._rpFlows.size, rpCount + 1);
+ switch (method) {
+ case "ready":
+ DOMIdentity._childProcessShutdown("my message manager");
+ break;
+
+ case "child-process-shutdown":
+ // We have to call this explicitly because there's no real
+ // dom window here.
+ FirefoxAccounts.RP.childProcessShutdown(mockedRP._mm);
+ break;
+
+ default:
+ break;
+ }
+ });
+
+ mockedRP._mm = "my message manager";
+ withSomebodySignedIn().then(() => {
+ FirefoxAccounts.RP.watch(mockedRP);
+ });
+
+ // fake a dom window context
+ DOMIdentity.newContext(mockedRP, mockedRP._mm);
+}
+
+var TESTS = [
+ test_overall,
+ test_mock,
+ test_watch_signed_in,
+ test_watch_signed_out,
+ test_request,
+ test_logout,
+ test_error,
+ test_child_process_shutdown,
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_identity.js b/toolkit/identity/tests/unit/test_identity.js
new file mode 100644
index 000000000..5e2206c2a
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_identity.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+function test_overall() {
+ do_check_neq(IDService, null);
+ run_next_test();
+}
+
+function test_mock_doc() {
+ do_test_pending();
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {
+ do_check_eq(action, 'coffee');
+ do_test_finished();
+ run_next_test();
+ });
+
+ mockedDoc.doCoffee();
+}
+
+function test_add_identity() {
+ IDService.reset();
+
+ IDService.addIdentity(TEST_USER);
+
+ let identities = IDService.RP.getIdentitiesForSite(TEST_URL);
+ do_check_eq(identities.result.length, 1);
+ do_check_eq(identities.result[0], TEST_USER);
+
+ run_next_test();
+}
+
+function test_select_identity() {
+ do_test_pending();
+
+ IDService.reset();
+
+ let id = "ishtar@mockmyid.com";
+ setup_test_identity(id, TEST_CERT, function() {
+ let gotAssertion = false;
+ let mockedDoc = mock_doc(null, TEST_URL, call_sequentially(
+ function(action, params) {
+ // ready emitted from first watch() call
+ do_check_eq(action, 'ready');
+ do_check_null(params);
+ },
+ // first the login call
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_neq(params, null);
+
+ // XXX - check that the assertion is for the right email
+
+ gotAssertion = true;
+ },
+ // then the ready call
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_null(params);
+
+ // we should have gotten the assertion already
+ do_check_true(gotAssertion);
+
+ do_test_finished();
+ run_next_test();
+ }));
+
+ // register the callbacks
+ IDService.RP.watch(mockedDoc);
+
+ // register the request UX observer
+ makeObserver("identity-request", function (aSubject, aTopic, aData) {
+ // do the select identity
+ // we expect this to succeed right away because of test_identity
+ // so we don't mock network requests or otherwise
+ IDService.selectIdentity(aSubject.wrappedJSObject.rpId, id);
+ });
+
+ // do the request
+ IDService.RP.request(mockedDoc.id, {});
+ });
+}
+
+function test_parse_good_email() {
+ var parsed = IDService.parseEmail('prime-minister@jed.gov');
+ do_check_eq(parsed.username, 'prime-minister');
+ do_check_eq(parsed.domain, 'jed.gov');
+ run_next_test();
+}
+
+function test_parse_bogus_emails() {
+ do_check_eq(null, IDService.parseEmail('@evil.org'));
+ do_check_eq(null, IDService.parseEmail('foo@bar@baz.com'));
+ do_check_eq(null, IDService.parseEmail('you@wellsfargo.com/accounts/transfer?to=dolske&amt=all'));
+ run_next_test();
+}
+
+var TESTS = [test_overall, test_mock_doc];
+
+TESTS.push(test_add_identity);
+TESTS.push(test_select_identity);
+TESTS.push(test_parse_good_email);
+TESTS.push(test_parse_bogus_emails);
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_identity_utils.js b/toolkit/identity/tests/unit/test_identity_utils.js
new file mode 100644
index 000000000..6ccc4e311
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_identity_utils.js
@@ -0,0 +1,46 @@
+
+"use strict";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/identity/IdentityUtils.jsm');
+
+function test_check_deprecated() {
+ let options = {
+ id: 123,
+ loggedInEmail: "jed@foo.com",
+ pies: 42
+ };
+
+ do_check_true(checkDeprecated(options, "loggedInEmail"));
+ do_check_false(checkDeprecated(options, "flans"));
+
+ run_next_test();
+}
+
+function test_check_renamed() {
+ let options = {
+ id: 123,
+ loggedInEmail: "jed@foo.com",
+ pies: 42
+ };
+
+ checkRenamed(options, "loggedInEmail", "loggedInUser");
+
+ // It moves loggedInEmail to loggedInUser
+ do_check_false(!!options.loggedInEmail);
+ do_check_eq(options.loggedInUser, "jed@foo.com");
+
+ run_next_test();
+}
+
+var TESTS = [
+ test_check_deprecated,
+ test_check_renamed
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_jwcrypto.js b/toolkit/identity/tests/unit/test_jwcrypto.js
new file mode 100644
index 000000000..f8fe82453
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_jwcrypto.js
@@ -0,0 +1,281 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict"
+
+Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
+ "resource://gre/modules/identity/jwcrypto.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "CryptoService",
+ "@mozilla.org/identity/crypto-service;1",
+ "nsIIdentityCryptoService");
+
+const RP_ORIGIN = "http://123done.org";
+const INTERNAL_ORIGIN = "browserid://";
+
+const SECOND_MS = 1000;
+const MINUTE_MS = SECOND_MS * 60;
+const HOUR_MS = MINUTE_MS * 60;
+
+function test_sanity() {
+ do_test_pending();
+
+ jwcrypto.generateKeyPair("DS160", function(err, kp) {
+ do_check_null(err);
+
+ do_test_finished();
+ run_next_test();
+ });
+}
+
+function test_generate() {
+ do_test_pending();
+ jwcrypto.generateKeyPair("DS160", function(err, kp) {
+ do_check_null(err);
+ do_check_neq(kp, null);
+
+ do_test_finished();
+ run_next_test();
+ });
+}
+
+function test_get_assertion() {
+ do_test_pending();
+
+ jwcrypto.generateKeyPair(
+ "DS160",
+ function(err, kp) {
+ jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, (err2, backedAssertion) => {
+ do_check_null(err2);
+
+ do_check_eq(backedAssertion.split("~").length, 2);
+ do_check_eq(backedAssertion.split(".").length, 3);
+
+ do_test_finished();
+ run_next_test();
+ });
+ });
+}
+
+function test_rsa() {
+ do_test_pending();
+ function checkRSA(err, kpo) {
+ do_check_neq(kpo, undefined);
+ log(kpo.serializedPublicKey);
+ let pk = JSON.parse(kpo.serializedPublicKey);
+ do_check_eq(pk.algorithm, "RS");
+/* TODO
+ do_check_neq(kpo.sign, null);
+ do_check_eq(typeof kpo.sign, "function");
+ do_check_neq(kpo.userID, null);
+ do_check_neq(kpo.url, null);
+ do_check_eq(kpo.url, INTERNAL_ORIGIN);
+ do_check_neq(kpo.exponent, null);
+ do_check_neq(kpo.modulus, null);
+
+ // TODO: should sign be async?
+ let sig = kpo.sign("This is a message to sign");
+
+ do_check_neq(sig, null);
+ do_check_eq(typeof sig, "string");
+ do_check_true(sig.length > 1);
+*/
+ do_test_finished();
+ run_next_test();
+ }
+
+ jwcrypto.generateKeyPair("RS256", checkRSA);
+}
+
+function test_dsa() {
+ do_test_pending();
+ function checkDSA(err, kpo) {
+ do_check_neq(kpo, undefined);
+ log(kpo.serializedPublicKey);
+ let pk = JSON.parse(kpo.serializedPublicKey);
+ do_check_eq(pk.algorithm, "DS");
+/* TODO
+ do_check_neq(kpo.sign, null);
+ do_check_eq(typeof kpo.sign, "function");
+ do_check_neq(kpo.userID, null);
+ do_check_neq(kpo.url, null);
+ do_check_eq(kpo.url, INTERNAL_ORIGIN);
+ do_check_neq(kpo.generator, null);
+ do_check_neq(kpo.prime, null);
+ do_check_neq(kpo.subPrime, null);
+ do_check_neq(kpo.publicValue, null);
+
+ let sig = kpo.sign("This is a message to sign");
+
+ do_check_neq(sig, null);
+ do_check_eq(typeof sig, "string");
+ do_check_true(sig.length > 1);
+*/
+ do_test_finished();
+ run_next_test();
+ }
+
+ jwcrypto.generateKeyPair("DS160", checkDSA);
+}
+
+function test_get_assertion_with_offset() {
+ do_test_pending();
+
+
+ // Use an arbitrary date in the past to ensure we don't accidentally pass
+ // this test with current dates, missing offsets, etc.
+ let serverMsec = Date.parse("Tue Oct 31 2000 00:00:00 GMT-0800");
+
+ // local clock skew
+ // clock is 12 hours fast; -12 hours offset must be applied
+ let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
+ let localMsec = serverMsec - localtimeOffsetMsec;
+
+ jwcrypto.generateKeyPair(
+ "DS160",
+ function(err, kp) {
+ jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
+ { duration: MINUTE_MS,
+ localtimeOffsetMsec: localtimeOffsetMsec,
+ now: localMsec},
+ function(err2, backedAssertion) {
+ do_check_null(err2);
+
+ // properly formed
+ let cert;
+ let assertion;
+ [cert, assertion] = backedAssertion.split("~");
+
+ do_check_eq(cert, "fake-cert");
+ do_check_eq(assertion.split(".").length, 3);
+
+ let components = extractComponents(assertion);
+
+ // Expiry is within two minutes, corrected for skew
+ let exp = parseInt(components.payload.exp, 10);
+ do_check_true(exp - serverMsec === MINUTE_MS);
+
+ do_test_finished();
+ run_next_test();
+ }
+ );
+ }
+ );
+}
+
+function test_assertion_lifetime() {
+ do_test_pending();
+
+ jwcrypto.generateKeyPair(
+ "DS160",
+ function(err, kp) {
+ jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
+ {duration: MINUTE_MS},
+ function(err2, backedAssertion) {
+ do_check_null(err2);
+
+ // properly formed
+ let cert;
+ let assertion;
+ [cert, assertion] = backedAssertion.split("~");
+
+ do_check_eq(cert, "fake-cert");
+ do_check_eq(assertion.split(".").length, 3);
+
+ let components = extractComponents(assertion);
+
+ // Expiry is within one minute, as we specified above
+ let exp = parseInt(components.payload.exp, 10);
+ do_check_true(Math.abs(Date.now() - exp) > 50 * SECOND_MS);
+ do_check_true(Math.abs(Date.now() - exp) <= MINUTE_MS);
+
+ do_test_finished();
+ run_next_test();
+ }
+ );
+ }
+ );
+}
+
+function test_audience_encoding_bug972582() {
+ let audience = "i-like-pie.com";
+
+ jwcrypto.generateKeyPair(
+ "DS160",
+ function(err, kp) {
+ do_check_null(err);
+ jwcrypto.generateAssertion("fake-cert", kp, audience,
+ function(err2, backedAssertion) {
+ do_check_null(err2);
+
+ let [cert, assertion] = backedAssertion.split("~");
+ let components = extractComponents(assertion);
+ do_check_eq(components.payload.aud, audience);
+
+ do_test_finished();
+ run_next_test();
+ }
+ );
+ }
+ );
+}
+
+// End of tests
+// Helper function follow
+
+function extractComponents(signedObject) {
+ if (typeof(signedObject) != 'string') {
+ throw new Error("malformed signature " + typeof(signedObject));
+ }
+
+ let parts = signedObject.split(".");
+ if (parts.length != 3) {
+ throw new Error("signed object must have three parts, this one has " + parts.length);
+ }
+
+ let headerSegment = parts[0];
+ let payloadSegment = parts[1];
+ let cryptoSegment = parts[2];
+
+ let header = JSON.parse(base64UrlDecode(headerSegment));
+ let payload = JSON.parse(base64UrlDecode(payloadSegment));
+
+ // Ensure well-formed header
+ do_check_eq(Object.keys(header).length, 1);
+ do_check_true(!!header.alg);
+
+ // Ensure well-formed payload
+ for (let field of ["exp", "aud"]) {
+ do_check_true(!!payload[field]);
+ }
+
+ return {header: header,
+ payload: payload,
+ headerSegment: headerSegment,
+ payloadSegment: payloadSegment,
+ cryptoSegment: cryptoSegment};
+}
+
+var TESTS = [
+ test_sanity,
+ test_generate,
+ test_get_assertion,
+ test_get_assertion_with_offset,
+ test_assertion_lifetime,
+ test_audience_encoding_bug972582,
+];
+
+TESTS = TESTS.concat([test_rsa, test_dsa]);
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_load_modules.js b/toolkit/identity/tests/unit/test_load_modules.js
new file mode 100644
index 000000000..4c531312c
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_load_modules.js
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const modules = [
+ "Identity.jsm",
+ "IdentityProvider.jsm",
+ "IdentityStore.jsm",
+ "jwcrypto.jsm",
+ "RelyingParty.jsm",
+ "Sandbox.jsm",
+];
+
+function run_test() {
+ for (let m of modules) {
+ let resource = "resource://gre/modules/identity/" + m;
+ Components.utils.import(resource, {});
+ do_print("loaded " + resource);
+ }
+}
diff --git a/toolkit/identity/tests/unit/test_log_utils.js b/toolkit/identity/tests/unit/test_log_utils.js
new file mode 100644
index 000000000..ac43c297d
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_log_utils.js
@@ -0,0 +1,74 @@
+
+"use strict";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+
+function toggle_debug() {
+ do_test_pending();
+
+ function Wrapper() {
+ this.init();
+ }
+ Wrapper.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+ observe: function observe(aSubject, aTopic, aData) {
+ if (aTopic === "nsPref:changed") {
+ // race condition?
+ do_check_eq(Logger._debug, true);
+ do_test_finished();
+ run_next_test();
+ }
+ },
+
+ init: function() {
+ Services.prefs.addObserver('toolkit.identity.debug', this, false);
+ }
+ };
+
+ var wrapper = new Wrapper();
+ Services.prefs.setBoolPref('toolkit.identity.debug', true);
+}
+
+// test that things don't break
+
+function logAlias(...args) {
+ Logger.log.apply(Logger, ["log alias"].concat(args));
+}
+function reportErrorAlias(...args) {
+ Logger.reportError.apply(Logger, ["report error alias"].concat(args));
+}
+
+function test_log() {
+ Logger.log("log test", "I like pie");
+ do_test_finished();
+ run_next_test();
+}
+
+function test_reportError() {
+ Logger.reportError("log test", "We are out of pies!!!");
+ do_test_finished();
+ run_next_test();
+}
+
+function test_wrappers() {
+ logAlias("I like potatoes");
+ do_test_finished();
+ reportErrorAlias("Too much red bull");
+}
+
+var TESTS = [
+// XXX fix me
+// toggle_debug,
+ test_log,
+ test_reportError,
+ test_wrappers,
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_minimalidentity.js b/toolkit/identity/tests/unit/test_minimalidentity.js
new file mode 100644
index 000000000..77c30c84f
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_minimalidentity.js
@@ -0,0 +1,223 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm",
+ "IdentityService");
+
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+Cu.import("resource://gre/modules/DOMIdentity.jsm");
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test_minimalidentity"].concat(aMessageArgs));
+}
+
+function test_overall() {
+ do_check_neq(MinimalIDService, null);
+ run_next_test();
+}
+
+function test_mock_doc() {
+ do_test_pending();
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {
+ do_check_eq(action, 'coffee');
+ do_test_finished();
+ run_next_test();
+ });
+
+ mockedDoc.doCoffee();
+}
+
+/*
+ * Test that the "identity-controller-watch" signal is emitted correctly
+ */
+function test_watch() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-watch", function (aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.id, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+ do_test_finished();
+ run_next_test();
+ });
+
+ MinimalIDService.RP.watch(mockedDoc);
+}
+
+/*
+ * Test that the "identity-controller-request" signal is emitted correctly
+ */
+function test_request() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-request", function (aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.id, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+ do_test_finished();
+ run_next_test();
+ });
+
+ MinimalIDService.RP.watch(mockedDoc);
+ MinimalIDService.RP.request(mockedDoc.id, {});
+}
+
+/*
+ * Test that the forceAuthentication flag can be sent
+ */
+function test_request_forceAuthentication() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-request", function (aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.id, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+ do_check_eq(aSubject.wrappedJSObject.forceAuthentication, true);
+ do_test_finished();
+ run_next_test();
+ });
+
+ MinimalIDService.RP.watch(mockedDoc);
+ MinimalIDService.RP.request(mockedDoc.id, {forceAuthentication: true});
+}
+
+/*
+ * Test that the issuer can be forced
+ */
+function test_request_forceIssuer() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-request", function (aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.id, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+ do_check_eq(aSubject.wrappedJSObject.issuer, "https://jed.gov");
+ do_test_finished();
+ run_next_test();
+ });
+
+ MinimalIDService.RP.watch(mockedDoc);
+ MinimalIDService.RP.request(mockedDoc.id, {issuer: "https://jed.gov"});
+}
+
+/*
+ * Test that the "identity-controller-logout" signal is emitted correctly
+ */
+function test_logout() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-logout", function (aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.id, mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ });
+
+ MinimalIDService.RP.watch(mockedDoc);
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+}
+
+/*
+ * Test that logout() before watch() fails gently
+ */
+
+function test_logoutBeforeWatch() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-logout", function() {
+ do_throw("How can we logout when watch was not called?");
+ });
+
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ do_test_finished();
+ run_next_test();
+}
+
+/*
+ * Test that request() before watch() fails gently
+ */
+
+function test_requestBeforeWatch() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+ makeObserver("identity-controller-request", function() {
+ do_throw("How can we request when watch was not called?");
+ });
+
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ do_test_finished();
+ run_next_test();
+}
+
+/*
+ * Test that internal unwatch() before watch() fails gently
+ */
+
+function test_unwatchBeforeWatch() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL);
+
+ MinimalIDService.RP.unwatch(mockedDoc.id, {});
+ do_test_finished();
+ run_next_test();
+}
+
+/*
+ * Test that the RP flow is cleaned up on child process shutdown
+ */
+
+function test_childProcessShutdown() {
+ do_test_pending();
+ let UNIQUE_MESSAGE_MANAGER = "i am a beautiful snowflake";
+ let initialRPCount = Object.keys(MinimalIDService.RP._rpFlows).length;
+
+ let mockedDoc = mock_doc(null, TEST_URL, (action, params) => {
+ if (action == "child-process-shutdown") {
+ // since there's no actual dom window connection, we have to
+ // do this bit manually here.
+ MinimalIDService.RP.childProcessShutdown(UNIQUE_MESSAGE_MANAGER);
+ }
+ });
+ mockedDoc._mm = UNIQUE_MESSAGE_MANAGER;
+
+ makeObserver("identity-controller-watch", function (aSubject, aTopic, aData) {
+ DOMIdentity._childProcessShutdown(UNIQUE_MESSAGE_MANAGER);
+ });
+
+ makeObserver("identity-child-process-shutdown", (aTopic, aSubject, aData) => {
+ do_check_eq(Object.keys(MinimalIDService.RP._rpFlows).length, initialRPCount);
+ do_test_finished();
+ run_next_test();
+ });
+
+ // fake a dom window context
+ DOMIdentity.newContext(mockedDoc, UNIQUE_MESSAGE_MANAGER);
+
+ MinimalIDService.RP.watch(mockedDoc);
+}
+
+var TESTS = [
+ test_overall,
+ test_mock_doc,
+ test_watch,
+ test_request,
+ test_request_forceAuthentication,
+ test_request_forceIssuer,
+ test_logout,
+ test_logoutBeforeWatch,
+ test_requestBeforeWatch,
+ test_unwatchBeforeWatch,
+ test_childProcessShutdown,
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_observer_topics.js b/toolkit/identity/tests/unit/test_observer_topics.js
new file mode 100644
index 000000000..8e5a89c91
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_observer_topics.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * By their nature, these tests duplicate some of the functionality of
+ * other tests for Identity, RelyingParty, and IdentityProvider.
+ *
+ * In particular, "identity-auth-complete" and
+ * "identity-login-state-changed" are tested in test_authentication.js
+ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+function test_smoke() {
+ do_check_neq(IDService, null);
+ run_next_test();
+}
+
+function test_identity_request() {
+ // In response to navigator.id.request(), initiate a login with user
+ // interaction by notifying observers of 'identity-request'
+
+ do_test_pending();
+
+ IDService.reset();
+
+ let id = "landru@mockmyid.com";
+ setup_test_identity(id, TEST_CERT, function() {
+ // deliberately adding a trailing final slash on the domain
+ // to test path composition
+ let mockedDoc = mock_doc(null, "http://jed.gov/", function() {});
+
+ // by calling watch() we create an rp flow.
+ IDService.RP.watch(mockedDoc);
+
+ // register the request UX observer
+ makeObserver("identity-request", function (aSubject, aTopic, aData) {
+ do_check_eq(aTopic, "identity-request");
+ do_check_eq(aData, null);
+
+ // check that all the URLs are properly resolved
+ let subj = aSubject.wrappedJSObject;
+ do_check_eq(subj.privacyPolicy, "http://jed.gov/pp.html");
+ do_check_eq(subj.termsOfService, "http://jed.gov/tos.html");
+
+ do_test_finished();
+ run_next_test();
+ });
+
+ let requestOptions = {
+ privacyPolicy: "/pp.html",
+ termsOfService: "/tos.html"
+ };
+ IDService.RP.request(mockedDoc.id, requestOptions);
+ });
+
+}
+
+function test_identity_auth() {
+ // see test_authentication.js for "identity-auth-complete"
+ // and "identity-login-state-changed"
+
+ do_test_pending();
+ let _provId = "bogus";
+
+ // Simulate what would be returned by IDService._fetchWellKnownFile
+ // for a given domain.
+ let idpParams = {
+ domain: "myfavoriteflan.com",
+ idpParams: {
+ authentication: "/foo/authenticate.html",
+ provisioning: "/foo/provision.html"
+ }
+ };
+
+ // Create an RP flow
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {});
+ IDService.RP.watch(mockedDoc);
+
+ // The identity-auth notification is sent up to the UX from the
+ // _doAuthentication function. Be ready to receive it and call
+ // beginAuthentication
+ makeObserver("identity-auth", function (aSubject, aTopic, aData) {
+ do_check_neq(aSubject, null);
+ do_check_eq(aTopic, "identity-auth");
+ do_check_eq(aData, "https://myfavoriteflan.com/foo/authenticate.html");
+
+ do_check_eq(aSubject.wrappedJSObject.provId, _provId);
+ do_test_finished();
+ run_next_test();
+ });
+
+ // Even though our provisioning flow id is bogus, IdentityProvider
+ // won't look at it until farther along in the authentication
+ // process. So this test can pass with a fake provId.
+ IDService.IDP._doAuthentication(_provId, idpParams);
+}
+
+var TESTS = [
+ test_smoke,
+ test_identity_request,
+ test_identity_auth,
+ ];
+
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_provisioning.js b/toolkit/identity/tests/unit/test_provisioning.js
new file mode 100644
index 000000000..c05805bef
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_provisioning.js
@@ -0,0 +1,242 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/identity/IdentityProvider.jsm");
+
+function check_provision_flow_done(provId) {
+ do_check_null(IdentityProvider._provisionFlows[provId]);
+}
+
+function test_begin_provisioning() {
+ do_test_pending();
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ // call .beginProvisioning()
+ IdentityProvider.beginProvisioning(caller);
+ }, function() {},
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ do_check_eq(email, TEST_USER);
+ do_check_true(duration_s > 0);
+ do_check_true(duration_s <= (24 * 3600));
+
+ do_test_finished();
+ run_next_test();
+ }
+ });
+}
+
+function test_raise_provisioning_failure() {
+ do_test_pending();
+ let _callerId = null;
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ // call .beginProvisioning()
+ _callerId = caller.id;
+ IdentityProvider.beginProvisioning(caller);
+ }, function(err) {
+ // this should be invoked with a populated error
+ do_check_neq(err, null);
+ do_check_true(err.indexOf("can't authenticate this email") > -1);
+
+ do_test_finished();
+ run_next_test();
+ },
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ // raise the failure as if we can't provision this email
+ IdentityProvider.raiseProvisioningFailure(_callerId, "can't authenticate this email");
+ }
+ });
+}
+
+function test_genkeypair_before_begin_provisioning() {
+ do_test_pending();
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ // call genKeyPair without beginProvisioning
+ IdentityProvider.genKeyPair(caller.id);
+ },
+ // expect this to be called with an error
+ function(err) {
+ do_check_neq(err, null);
+
+ do_test_finished();
+ run_next_test();
+ },
+ {
+ // this should not be called at all!
+ genKeyPairCallback: function(pk) {
+ // a test that will surely fail because we shouldn't be here.
+ do_check_true(false);
+
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ );
+}
+
+function test_genkeypair() {
+ do_test_pending();
+ let _callerId = null;
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ _callerId = caller.id;
+ IdentityProvider.beginProvisioning(caller);
+ },
+ function(err) {
+ // should not be called!
+ do_check_true(false);
+
+ do_test_finished();
+ run_next_test();
+ },
+ {
+ beginProvisioningCallback: function(email, time_s) {
+ IdentityProvider.genKeyPair(_callerId);
+ },
+ genKeyPairCallback: function(kp) {
+ do_check_neq(kp, null);
+
+ // yay!
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ );
+}
+
+// we've already ensured that genkeypair can't be called
+// before beginProvisioning, so this test should be enough
+// to ensure full sequential call of the 3 APIs.
+function test_register_certificate_before_genkeypair() {
+ do_test_pending();
+ let _callerID = null;
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ // do the right thing for beginProvisioning
+ _callerID = caller.id;
+ IdentityProvider.beginProvisioning(caller);
+ },
+ // expect this to be called with an error
+ function(err) {
+ do_check_neq(err, null);
+
+ do_test_finished();
+ run_next_test();
+ },
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ // now we try to register cert but no keygen has been done
+ IdentityProvider.registerCertificate(_callerID, "fake-cert");
+ }
+ }
+ );
+}
+
+function test_register_certificate() {
+ do_test_pending();
+ let _callerId = null;
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ _callerId = caller.id;
+ IdentityProvider.beginProvisioning(caller);
+ },
+ function(err) {
+ // we should be cool!
+ do_check_null(err);
+
+ // check that the cert is there
+ let identity = get_idstore().fetchIdentity(TEST_USER);
+ do_check_neq(identity, null);
+ do_check_eq(identity.cert, "fake-cert-42");
+
+ do_execute_soon(function check_done() {
+ // cleanup will happen after the callback is called
+ check_provision_flow_done(_callerId);
+
+ do_test_finished();
+ run_next_test();
+ });
+ },
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ IdentityProvider.genKeyPair(_callerId);
+ },
+ genKeyPairCallback: function(pk) {
+ IdentityProvider.registerCertificate(_callerId, "fake-cert-42");
+ }
+ }
+ );
+}
+
+
+function test_get_assertion_after_provision() {
+ do_test_pending();
+ let _callerId = null;
+
+ setup_provisioning(
+ TEST_USER,
+ function(caller) {
+ _callerId = caller.id;
+ IdentityProvider.beginProvisioning(caller);
+ },
+ function(err) {
+ // we should be cool!
+ do_check_null(err);
+
+ // check that the cert is there
+ let identity = get_idstore().fetchIdentity(TEST_USER);
+ do_check_neq(identity, null);
+ do_check_eq(identity.cert, "fake-cert-42");
+
+ do_execute_soon(function check_done() {
+ // cleanup will happen after the callback is called
+ check_provision_flow_done(_callerId);
+
+ do_test_finished();
+ run_next_test();
+ });
+ },
+ {
+ beginProvisioningCallback: function(email, duration_s) {
+ IdentityProvider.genKeyPair(_callerId);
+ },
+ genKeyPairCallback: function(pk) {
+ IdentityProvider.registerCertificate(_callerId, "fake-cert-42");
+ }
+ }
+ );
+
+}
+
+var TESTS = [];
+
+TESTS.push(test_begin_provisioning);
+TESTS.push(test_raise_provisioning_failure);
+TESTS.push(test_genkeypair_before_begin_provisioning);
+TESTS.push(test_genkeypair);
+TESTS.push(test_register_certificate_before_genkeypair);
+TESTS.push(test_register_certificate);
+TESTS.push(test_get_assertion_after_provision);
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_relying_party.js b/toolkit/identity/tests/unit/test_relying_party.js
new file mode 100644
index 000000000..e78d22779
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_relying_party.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "RelyingParty",
+ "resource://gre/modules/identity/RelyingParty.jsm");
+
+function resetState() {
+ get_idstore().reset();
+ RelyingParty.reset();
+}
+
+function test_watch_loggedin_ready() {
+ do_test_pending();
+
+ resetState();
+
+ let id = TEST_USER;
+ setup_test_identity(id, TEST_CERT, function() {
+ let store = get_idstore();
+
+ // set it up so we're supposed to be logged in to TEST_URL
+ store.setLoginState(TEST_URL, true, id);
+ RelyingParty.watch(mock_doc(id, TEST_URL, function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+
+ do_test_finished();
+ run_next_test();
+ }));
+ });
+}
+
+function test_watch_loggedin_login() {
+ do_test_pending();
+
+ resetState();
+
+ let id = TEST_USER;
+ setup_test_identity(id, TEST_CERT, function() {
+ let store = get_idstore();
+
+ // set it up so we're supposed to be logged in to TEST_URL
+ store.setLoginState(TEST_URL, true, id);
+
+ // check for first a login() call, then a ready() call
+ RelyingParty.watch(mock_doc(null, TEST_URL, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_neq(params, null);
+ },
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_null(params);
+
+ do_test_finished();
+ run_next_test();
+ }
+ )));
+ });
+}
+
+function test_watch_loggedin_logout() {
+ do_test_pending();
+
+ resetState();
+
+ let id = TEST_USER;
+ let other_id = "otherid@foo.com";
+ setup_test_identity(other_id, TEST_CERT, function() {
+ setup_test_identity(id, TEST_CERT, function() {
+ let store = get_idstore();
+
+ // set it up so we're supposed to be logged in to TEST_URL
+ // with id, not other_id
+ store.setLoginState(TEST_URL, true, id);
+
+ // this should cause a login with an assertion for id,
+ // not for other_id
+ RelyingParty.watch(mock_doc(other_id, TEST_URL, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_neq(params, null);
+ },
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_null(params);
+
+ do_test_finished();
+ run_next_test();
+ }
+ )));
+ });
+ });
+}
+
+function test_watch_notloggedin_ready() {
+ do_test_pending();
+
+ resetState();
+
+ RelyingParty.watch(mock_doc(null, TEST_URL, function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+
+ do_test_finished();
+ run_next_test();
+ }));
+}
+
+function test_watch_notloggedin_logout() {
+ do_test_pending();
+
+ resetState();
+
+ RelyingParty.watch(mock_doc(TEST_USER, TEST_URL, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+
+ let store = get_idstore();
+ do_check_null(store.getLoginState(TEST_URL));
+ },
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ do_test_finished();
+ run_next_test();
+ }
+ )));
+}
+
+function test_request() {
+ do_test_pending();
+
+ // set up a watch, to be consistent
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {
+ // this isn't going to be called for now
+ // XXX but it is called - is that bad?
+ });
+
+ RelyingParty.watch(mockedDoc);
+
+ // be ready for the UX identity-request notification
+ makeObserver("identity-request", function (aSubject, aTopic, aData) {
+ do_check_neq(aSubject, null);
+
+ do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+
+ do_test_finished();
+ run_next_test();
+ });
+
+ RelyingParty.request(mockedDoc.id, {});
+}
+
+/*
+ * ensure the forceAuthentication param can be passed through
+ */
+function test_request_forceAuthentication() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {});
+
+ RelyingParty.watch(mockedDoc);
+
+ makeObserver("identity-request", function(aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.forceAuthentication, true);
+ do_test_finished();
+ run_next_test();
+ });
+
+ RelyingParty.request(mockedDoc.id, {forceAuthentication: true});
+}
+
+/*
+ * ensure the issuer can be forced
+ */
+function test_request_forceIssuer() {
+ do_test_pending();
+
+ let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {});
+
+ RelyingParty.watch(mockedDoc);
+
+ makeObserver("identity-request", function(aSubject, aTopic, aData) {
+ do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+ do_check_eq(aSubject.wrappedJSObject.issuer, "https://ozten.co.uk");
+ do_test_finished();
+ run_next_test();
+ });
+
+ RelyingParty.request(mockedDoc.id, {issuer: "https://ozten.co.uk"});
+}
+function test_logout() {
+ do_test_pending();
+
+ resetState();
+
+ let id = TEST_USER;
+ setup_test_identity(id, TEST_CERT, function() {
+ let store = get_idstore();
+
+ // set it up so we're supposed to be logged in to TEST_URL
+ store.setLoginState(TEST_URL, true, id);
+
+ let doLogout;
+ let mockedDoc = mock_doc(id, TEST_URL, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+
+ do_timeout(100, doLogout);
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+
+ do_test_finished();
+ run_next_test();
+ }));
+
+ doLogout = function() {
+ RelyingParty.logout(mockedDoc.id);
+ do_check_false(store.getLoginState(TEST_URL).isLoggedIn);
+ do_check_eq(store.getLoginState(TEST_URL).email, TEST_USER);
+ };
+
+ RelyingParty.watch(mockedDoc);
+ });
+}
+
+var TESTS = [
+ test_watch_loggedin_ready,
+ test_watch_loggedin_login,
+ test_watch_loggedin_logout,
+ test_watch_notloggedin_ready,
+ test_watch_notloggedin_logout,
+ test_request,
+ test_request_forceAuthentication,
+ test_request_forceIssuer,
+ test_logout,
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_store.js b/toolkit/identity/tests/unit/test_store.js
new file mode 100644
index 000000000..1cd9cc4dd
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_store.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+function test_id_store() {
+ // XXX - this is ugly, peaking in like this into IDService
+ // probably should instantiate our own.
+ var store = get_idstore();
+
+ // try adding an identity
+ store.addIdentity(TEST_USER, TEST_PRIVKEY, TEST_CERT);
+ do_check_neq(store.getIdentities()[TEST_USER], null);
+ do_check_eq(store.getIdentities()[TEST_USER].cert, TEST_CERT);
+
+ // does fetch identity work?
+ do_check_neq(store.fetchIdentity(TEST_USER), null);
+ do_check_eq(store.fetchIdentity(TEST_USER).cert, TEST_CERT);
+
+ // clear the cert should keep the identity but not the cert
+ store.clearCert(TEST_USER);
+ do_check_neq(store.getIdentities()[TEST_USER], null);
+ do_check_null(store.getIdentities()[TEST_USER].cert);
+
+ // remove it should remove everything
+ store.removeIdentity(TEST_USER);
+ do_check_eq(store.getIdentities()[TEST_USER], undefined);
+
+ // act like we're logged in to TEST_URL
+ store.setLoginState(TEST_URL, true, TEST_USER);
+ do_check_neq(store.getLoginState(TEST_URL), null);
+ do_check_true(store.getLoginState(TEST_URL).isLoggedIn);
+ do_check_eq(store.getLoginState(TEST_URL).email, TEST_USER);
+
+ // log out
+ store.setLoginState(TEST_URL, false, TEST_USER);
+ do_check_neq(store.getLoginState(TEST_URL), null);
+ do_check_false(store.getLoginState(TEST_URL).isLoggedIn);
+
+ // email is still set
+ do_check_eq(store.getLoginState(TEST_URL).email, TEST_USER);
+
+ // not logged into other site
+ do_check_null(store.getLoginState(TEST_URL2));
+
+ // clear login state
+ store.clearLoginState(TEST_URL);
+ do_check_null(store.getLoginState(TEST_URL));
+ do_check_null(store.getLoginState(TEST_URL2));
+
+ run_next_test();
+}
+
+var TESTS = [test_id_store, ];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/test_well-known.js b/toolkit/identity/tests/unit/test_well-known.js
new file mode 100644
index 000000000..5e86f5ae4
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_well-known.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "IDService",
+ "resource://gre/modules/identity/Identity.jsm",
+ "IdentityService");
+
+const WELL_KNOWN_PATH = "/.well-known/browserid";
+
+var SERVER_PORT = 8080;
+
+// valid IDP
+function test_well_known_1() {
+ do_test_pending();
+
+ let server = new HttpServer();
+ server.registerFile(WELL_KNOWN_PATH, do_get_file("data/idp_1" + WELL_KNOWN_PATH));
+ server.start(SERVER_PORT);
+ let hostPort = "localhost:" + SERVER_PORT;
+
+ function check_well_known(aErr, aCallbackObj) {
+ do_check_null(aErr);
+ do_check_eq(aCallbackObj.domain, hostPort);
+ let idpParams = aCallbackObj.idpParams;
+ do_check_eq(idpParams['public-key'].algorithm, "RS");
+ do_check_eq(idpParams.authentication, "/browserid/sign_in.html");
+ do_check_eq(idpParams.provisioning, "/browserid/provision.html");
+
+ do_test_finished();
+ server.stop(run_next_test);
+ }
+
+ IDService._fetchWellKnownFile(hostPort, check_well_known, "http");
+}
+
+// valid domain, non-exixtent browserid file
+function test_well_known_404() {
+ do_test_pending();
+
+ let server = new HttpServer();
+ // Don't register the well-known file
+ // Change ports to avoid HTTP caching
+ SERVER_PORT++;
+ server.start(SERVER_PORT);
+
+ let hostPort = "localhost:" + SERVER_PORT;
+
+ function check_well_known_404(aErr, aCallbackObj) {
+ do_check_eq("Error", aErr);
+ do_check_eq(undefined, aCallbackObj);
+ do_test_finished();
+ server.stop(run_next_test);
+ }
+
+ IDService._fetchWellKnownFile(hostPort, check_well_known_404, "http");
+}
+
+// valid domain, invalid browserid file (no "provisioning" member)
+function test_well_known_invalid_1() {
+ do_test_pending();
+
+ let server = new HttpServer();
+ server.registerFile(WELL_KNOWN_PATH, do_get_file("data/idp_invalid_1" + WELL_KNOWN_PATH));
+ // Change ports to avoid HTTP caching
+ SERVER_PORT++;
+ server.start(SERVER_PORT);
+
+ let hostPort = "localhost:" + SERVER_PORT;
+
+ function check_well_known_invalid_1(aErr, aCallbackObj) {
+ // check for an error message
+ do_check_true(aErr && aErr.length > 0);
+ do_check_eq(undefined, aCallbackObj);
+ do_test_finished();
+ server.stop(run_next_test);
+ }
+
+ IDService._fetchWellKnownFile(hostPort, check_well_known_invalid_1, "http");
+}
+
+var TESTS = [test_well_known_1, test_well_known_404, test_well_known_invalid_1];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/identity/tests/unit/xpcshell.ini b/toolkit/identity/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..8ef9b79bc
--- /dev/null
+++ b/toolkit/identity/tests/unit/xpcshell.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+head = head_identity.js
+tail = tail_identity.js
+skip-if = (appname != "b2g" || toolkit == 'gonk')
+support-files =
+ data/idp_1/.well-known/browserid
+ data/idp_invalid_1/.well-known/browserid
+
+# Test load modules first so syntax failures are caught early.
+[test_load_modules.js]
+[test_minimalidentity.js]
+[test_firefox_accounts.js]
+
+[test_identity_utils.js]
+[test_log_utils.js]
+[test_authentication.js]
+[test_crypto_service.js]
+[test_identity.js]
+[test_jwcrypto.js]
+[test_observer_topics.js]
+[test_provisioning.js]
+[test_relying_party.js]
+[test_store.js]
+[test_well-known.js]