Bug 1022181 - Mobile ID Tests. Part 2: Mobile Identity Service Tests. r=jedp, a=lmandel
authorFernando Jiménez <ferjmoreno@gmail.com>
Tue, 08 Jul 2014 19:57:44 +0200
changeset 207859 bcd461c3bb21c617935569cc524ff8c4dad9acf6
parent 207858 620e68b0053dda0faba0a747358cfc8a81ce86f7
child 207860 629db6ea47881f7dac3491fcaac2914a227c7a90
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjedp, lmandel
bugs1022181
milestone32.0a2
Bug 1022181 - Mobile ID Tests. Part 2: Mobile Identity Service Tests. r=jedp, a=lmandel
services/mobileid/MobileIdentityManager.jsm
services/mobileid/moz.build
services/mobileid/tests/moz.build
services/mobileid/tests/xpcshell/head.js
services/mobileid/tests/xpcshell/test_mobileid_manager.js
services/mobileid/tests/xpcshell/xpcshell.ini
--- a/services/mobileid/MobileIdentityManager.jsm
+++ b/services/mobileid/MobileIdentityManager.jsm
@@ -1,15 +1,15 @@
 /* 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/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [];
+this.EXPORTED_SYMBOLS = ["MobileIdentityManager"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
 Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
@@ -57,17 +57,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIRadioInterfaceLayer");
 
 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
                                    "@mozilla.org/ril/content-helper;1",
                                    "nsIIccProvider");
 #endif
 
 
-let MobileIdentityManager = {
+this.MobileIdentityManager = {
 
   init: function() {
     log.debug("MobileIdentityManager init");
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     ppmm.addMessageListener(GET_ASSERTION_IPC_MSG, this);
     this.messageManagers = {};
     this.keyPairs = {};
     this.certificates = {};
@@ -101,21 +101,20 @@ let MobileIdentityManager = {
   },
 
 
   /*********************************************************
    * Getters
    ********************************************************/
 
   get iccInfo() {
-#ifdef MOZ_B2G_RIL
     if (this._iccInfo) {
       return this._iccInfo;
     }
-
+#ifdef MOZ_B2G_RIL
     this._iccInfo = [];
     for (let i = 0; i < gRil.numRadioInterfaces; i++) {
       let rilContext = gRil.getRadioInterface(i).rilContext;
       if (!rilContext) {
         log.warn("Tried to get the RIL context for an invalid service ID " + i);
         continue;
       }
       let info = rilContext.iccInfo;
@@ -253,17 +252,19 @@ let MobileIdentityManager = {
 
   getVerificationOptions: function() {
     log.debug("getVerificationOptions");
     // We try to get if we already have credentials for any of the inserted
     // SIM cards if any is available and we try to get the possible
     // verification mechanisms for these SIM cards.
     // All this information will be stored in iccInfo.
     if (!this.iccInfo || !this.iccInfo.length) {
-      return Promise.resolve();
+      let deferred = Promise.defer();
+      deferred.resolve(null);
+      return deferred.promise;
     }
 
     let promises = [];
     for (let i = 0; i < this.iccInfo.length; i++) {
       promises.push(this.getVerificationOptionsForIcc(i));
     }
     return Promise.all(promises);
   },
@@ -314,16 +315,35 @@ let MobileIdentityManager = {
         deferred.resolve(signedCert.cert);
       },
       deferred.reject
     );
     return deferred.promise;
   },
 
   /*********************************************************
+   * Setters (for test only purposes)
+   ********************************************************/
+  set ui(aUi) {
+    this._ui = aUi;
+  },
+
+  set credStore(aCredStore) {
+    this._credStore = aCredStore;
+  },
+
+  set client(aClient) {
+    this._client = aClient;
+  },
+
+  set iccInfo(aIccInfo) {
+    this._iccInfo = aIccInfo;
+  },
+
+  /*********************************************************
    * UI callbacks
    ********************************************************/
 
   onUICancel: function() {
     log.debug("UI cancel");
     if (this.activeVerificationFlow) {
       this.activeVerificationFlow.cleanup(true);
     }
@@ -541,27 +561,27 @@ let MobileIdentityManager = {
         phoneInfoArray.push(phoneInfo);
       }
     }
 
     return this.ui.startFlow(aManifestURL, phoneInfoArray)
     .then(
       (result) => {
         if (!result ||
-            (!result.phoneNumber && !result.serviceId)) {
+            (!result.phoneNumber && (result.serviceId === undefined))) {
           return Promise.reject(ERROR_INTERNAL_INVALID_PROMPT_RESULT);
         }
 
         let msisdn;
         let mcc;
 
         // If the user selected one of the existing SIM cards we have to check
         // that we either have the MSISDN for that SIM or we can do a silent
         // verification that does not require us to have the MSISDN in advance.
-        if (result.serviceId) {
+        if (result.serviceId !== undefined) {
           let icc = this.iccInfo[result.serviceId];
           log.debug("icc ${}", icc);
           if (!icc || !icc.msisdn && !icc.canDoSilentVerification) {
             return Promise.reject(ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
           }
           msisdn = icc.msisdn;
           mcc = icc.mcc;
         } else {
@@ -755,17 +775,17 @@ let MobileIdentityManager = {
     let uri = Services.io.newURI(aPrincipal.origin, null, null);
     let principal = securityManager.getAppCodebasePrincipal(
       uri, aPrincipal.appid, aPrincipal.isInBrowserElement);
     let manifestURL = appsService.getManifestURLByLocalId(aPrincipal.appId);
 
     // First of all we look if we already have credentials for this origin.
     // If we don't have credentials it means that it is the first time that
     // the caller requested an assertion.
-    return this.credStore.getByOrigin(aPrincipal.origin)
+    this.credStore.getByOrigin(aPrincipal.origin)
     .then(
       (creds) => {
         log.debug("creds ${creds} - ${origin}", { creds: creds,
                                                   origin: aPrincipal.origin });
         if (!creds || !creds.sessionToken) {
           log.debug("No credentials");
           return;
         }
@@ -836,17 +856,18 @@ let MobileIdentityManager = {
         // is already granted and stored so we just progress the credentials.
         if (creds) {
           let permission = permissionManager.testPermissionFromPrincipal(
             principal,
             MOBILEID_PERM
           );
           if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
             return creds;
-          } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
+          } else if (permission == Ci.nsIPermissionManager.DENY_ACTION ||
+                     permission == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
             return Promise.reject(ERROR_PERMISSION_DENIED);
           }
           return this.promptAndVerify(principal, manifestURL, creds);
         }
         return this.promptAndVerify(principal, manifestURL);
       }
     )
     .then(
--- a/services/mobileid/moz.build
+++ b/services/mobileid/moz.build
@@ -1,16 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 PARALLEL_DIRS += ['interfaces']
 
+TEST_DIRS += ['tests']
+
 EXTRA_JS_MODULES += [
     'MobileIdentityClient.jsm',
     'MobileIdentityCommon.jsm',
     'MobileIdentityCredentialsStore.jsm',
     'MobileIdentitySmsMoMtVerificationFlow.jsm',
     'MobileIdentitySmsMtVerificationFlow.jsm',
     'MobileIdentityUIGlueCommon.jsm',
     'MobileIdentityVerificationFlow.jsm'
new file mode 100644
--- /dev/null
+++ b/services/mobileid/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/services/mobileid/tests/xpcshell/head.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+"use strict";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+(function initMobileIdTestingInfrastructure() {
+  do_get_profile();
+
+  Services.prefs.setCharPref("services.mobileid.loglevel", "Debug");
+
+}).call(this);
new file mode 100644
--- /dev/null
+++ b/services/mobileid/tests/xpcshell/test_mobileid_manager.js
@@ -0,0 +1,1235 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Cm = Components.manager;
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// We need to set the server url pref before importing MobileIdentityManager.
+// Otherwise, it won't be able to import it as getting the non existing pref
+// will throw.
+Services.prefs.setCharPref("services.mobileid.server.uri",
+                           "https://dummyurl.com");
+
+// Set do_printging on.
+Services.prefs.setCharPref("services.mobileid.loglevel", "do_print");
+
+Cu.import("resource://gre/modules/MobileIdentityManager.jsm");
+Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
+
+const DEBUG = false;
+
+const GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion";
+const GET_ASSERTION_RETURN_OK = "MobileId:GetAssertion:Return:OK";
+const GET_ASSERTION_RETURN_KO = "MobileId:GetAssertion:Return:KO";
+
+// === Globals ===
+
+const ORIGIN = "app://afakeorigin";
+const PRINCIPAL = {
+  origin: ORIGIN,
+  appId: "123"
+};
+const PHONE_NUMBER = "+34666555444";
+const ANOTHER_PHONE_NUMBER = "+44123123123";
+const VERIFICATION_CODE = "123456";
+const SESSION_TOKEN = "aSessionToken";
+const ICC_ID = "aIccId";
+const MNC = "aMnc";
+const MCC = 214;
+const OPERATOR = "aOperator";
+const CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9."
+
+// === Helpers ===
+
+function addPermission(aOrigin, aAction) {
+  let uri = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService)
+              .newURI(aOrigin, null, null);
+  let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                     .getService(Ci.nsIScriptSecurityManager)
+                     .getNoAppCodebasePrincipal(uri);
+  let pm = Cc["@mozilla.org/permissionmanager;1"]
+             .getService(Ci.nsIPermissionManager);
+  pm.addFromPrincipal(_principal, MOBILEID_PERM, aAction);
+}
+
+function removePermission(aOrigin) {
+  let uri = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService)
+              .newURI(aOrigin, null, null);
+  let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                     .getService(Ci.nsIScriptSecurityManager)
+                     .getNoAppCodebasePrincipal(uri);
+  let pm = Cc["@mozilla.org/permissionmanager;1"]
+             .getService(Ci.nsIPermissionManager);
+  pm.removeFromPrincipal(_principal, MOBILEID_PERM);
+}
+
+// === Mocks ===
+
+let Mock = function(aOptions) {
+  if (!aOptions) {
+    aOptions = {};
+  }
+  this._options = aOptions;
+  this._spied = {};
+};
+
+Mock.prototype = {
+  _: function(aMethod) {
+    DEBUG && do_print("_ " + aMethod + JSON.stringify(this._spied));
+    let self = this;
+    return {
+      callsLength: function(aNumberOfCalls) {
+        if (aNumberOfCalls == 0) {
+          do_check_eq(self._spied[aMethod], undefined);
+          return;
+        }
+        do_check_eq(self._spied[aMethod].length, aNumberOfCalls);
+      },
+      call: function(aCallNumber) {
+        return {
+          arg: function(aArgNumber, aValue) {
+            let _arg = self._spied[aMethod][aCallNumber - 1][aArgNumber - 1];
+            if (Array.isArray(aValue)) {
+              do_check_eq(_arg.length, aValue.length)
+              for (let i = 0; i < _arg.length; i++) {
+                do_check_eq(_arg[i], aValue[i]);
+              }
+              return;
+            }
+
+            if (typeof aValue === 'object') {
+              do_check_eq(JSON.stringify(_arg), JSON.stringify(aValue));
+              return;
+            }
+
+            do_check_eq(_arg, aValue);
+          }
+        }
+      }
+    }
+  },
+
+  _spy: function(aMethod, aArgs) {
+    DEBUG && do_print(aMethod + " - " + JSON.stringify(aArgs));
+    if (!this._spied[aMethod]) {
+      this._spied[aMethod] = [];
+    }
+    this._spied[aMethod].push(aArgs);
+  },
+
+  getSpiedCalls: function(aMethod) {
+    return this._spied[aMethod];
+  }
+};
+
+// UI Glue mock up.
+let MockUi = function(aOptions) {
+  Mock.call(this, aOptions);
+};
+
+MockUi.prototype = {
+  __proto__: Mock.prototype,
+
+  _startFlowResult: {
+    phoneNumber: PHONE_NUMBER
+  },
+
+  _verifyCodePromptResult: {
+    verificationCode: VERIFICATION_CODE
+  },
+
+  startFlow: function() {
+    this._spy("startFlow", arguments);
+    return Promise.resolve(this._options.startFlowResult ||
+                           this._startFlowResult);
+  },
+
+  verificationCodePrompt: function() {
+    this._spy("verifyCodePrompt", arguments);
+    return Promise.resolve(this._options.verificationCodePromptResult ||
+                           this._verifyCodePromptResult);
+  },
+
+  verify: function() {
+    this._spy("verify", arguments);
+  },
+
+  error: function() {
+    this._spy("error", arguments);
+  },
+
+  verified: function() {
+    this._spy("verified", arguments);
+  },
+
+  set oncancel(aCallback) {
+  },
+
+  set onresendcode(aCallback) {
+  }
+};
+
+// Save original credential store instance.
+const kMobileIdentityCredStore = MobileIdentityManager.credStore;
+
+// Credentials store mock up.
+let MockCredStore = function(aOptions) {
+  Mock.call(this, aOptions);
+};
+
+MockCredStore.prototype = {
+  __proto__: Mock.prototype,
+
+  _getByOriginResult: null,
+
+  _getByMsisdnResult: null,
+
+  _getByIccIdResult: null,
+
+  getByOrigin: function() {
+    this._spy("getByOrigin", arguments);
+    return Promise.resolve(this._options.getByOriginResult ||
+                           this._getByOriginResult);
+  },
+
+  getByMsisdn: function() {
+    this._spy("getByMsisdn", arguments);
+    return Promise.resolve(this._options.getByMsisdnResult ||
+                           this._getByMsisdnResult);
+  },
+
+  getByIccId: function() {
+    this._spy("getByIccId", arguments);
+    return Promise.resolve(this._options.getByIccIdResult ||
+                           this._getByIccIdResult);
+  },
+
+  add: function() {
+    this._spy("add", arguments);
+    return Promise.resolve();
+  },
+
+  setDeviceIccIds: function() {
+    this._spy("setDeviceIccIds", arguments);
+    return Promise.resolve();
+  },
+
+  removeOrigin: function() {
+    this._spy("removeOrigin", arguments);
+    return Promise.resolve();
+  }
+};
+
+// Save original client instance.
+const kMobileIdentityClient = MobileIdentityManager.client;
+
+// Client mock up.
+let MockClient = function(aOptions) {
+  Mock.call(this, aOptions);
+};
+
+MockClient.prototype = {
+
+  __proto__: Mock.prototype,
+
+  _discoverResult: {
+    verificationMethods: ["sms/momt", "sms/mt"],
+    verificationDetails: {
+      "sms/mt": {
+        mtSender: "123",
+        url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
+      },
+      "sms/momt": {
+        mtSender: "123",
+        moVerifier: "234"
+      }
+    }
+  },
+
+  _registerResult: {
+    msisdnSessionToken: SESSION_TOKEN
+  },
+
+  _smsMtVerifyResult: {},
+
+  _verifyCodeResult: {
+    msisdn: PHONE_NUMBER
+  },
+
+  _signResult: {
+    cert: CERTIFICATE
+  },
+
+  hawk: {
+    now: function() {
+      return Date.now();
+    }
+  },
+
+  discover: function() {
+    this._spy("discover", arguments);
+    return Promise.resolve(this._options.discoverResult ||
+                           this._discoverResult);
+  },
+
+  register: function() {
+    this._spy("register", arguments);
+    return Promise.resolve(this._options.registerResult ||
+                           this._registerResult);
+  },
+
+  smsMtVerify: function() {
+    this._spy("smsMtVerify", arguments);
+    return Promise.resolve(this._options.smsMtVerifyResult ||
+                           this._smsMtVerifyResult);
+  },
+
+  verifyCode: function() {
+    this._spy("verifyCode", arguments);
+    return Promise.resolve(this._options.verifyCodeResult ||
+                           this._verifyCodeResult);
+  },
+
+  sign: function() {
+    this._spy("sign", arguments);
+    return Promise.resolve(this._options.signResult || this._signResult);
+  }
+};
+
+// The test rely on having an app registered. Otherwise, it will throw.
+// Override XULAppInfo.
+const XUL_APP_INFO_UUID = Components.ID("{84fdc459-d96d-421c-9bff-a8193233ae75}");
+const XUL_APP_INFO_CONTRACT_ID = "@mozilla.org/xre/app-info;1";
+
+let (XULAppInfo = {
+  vendor: "Mozilla",
+  name: "MobileIdTest",
+  ID: "{230de50e-4cd1-11dc-8314-0800200b9a66}",
+  version: "1",
+  appBuildID: "2007010101",
+  platformVersion: "",
+  platformBuildID: "2007010101",
+  inSafeMode: false,
+  logConsoleErrors: true,
+  OS: "XPCShell",
+  XPCOMABI: "noarch-spidermonkey",
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIXULAppInfo,
+    Ci.nsIXULRuntime,
+  ])
+}) {
+  let XULAppInfoFactory = {
+    createInstance: function (outer, iid) {
+      if (outer != null) {
+        throw Cr.NS_ERROR_NO_AGGREGATION;
+      }
+      return XULAppInfo.QueryInterface(iid);
+    }
+  };
+  Cm.QueryInterface(Ci.nsIComponentRegistrar)
+    .registerFactory(XUL_APP_INFO_UUID,
+                     "XULAppInfo",
+                     XUL_APP_INFO_CONTRACT_ID,
+                     XULAppInfoFactory);
+}
+
+// === Global cleanup ===
+
+function cleanup() {
+  MobileIdentityManager.credStore = kMobileIdentityCredStore;
+  MobileIdentityManager.client = kMobileIdentityClient;
+  MobileIdentityManager.ui = null;
+  MobileIdentityManager.iccInfo = null;
+}
+
+// Unregister mocks and restore original code.
+do_register_cleanup(cleanup);
+
+// === Tests ===
+function run_test() {
+  run_next_test();
+}
+
+add_test(function() {
+  do_print("= Initial state =");
+  do_check_neq(MobileIdentityManager, undefined);
+  run_next_test();
+});
+
+add_test(function() {
+  do_print("= No credentials - No ICC - User MSISDN - External - OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore();
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient({
+    discoverResult: {
+      verificationMethods: ["sms/mt"],
+      verificationDetails: {
+        "sms/mt": {
+          mtSender: "123",
+          url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
+        }
+      }
+    }
+  });
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(1);
+      credStore._("getByMsisdn").call(1).arg(1, PHONE_NUMBER);
+      credStore._("add").callsLength(1);
+      credStore._("add").call(1).arg(1, undefined);
+      credStore._("add").call(1).arg(2, PHONE_NUMBER);
+      credStore._("add").call(1).arg(3, ORIGIN);
+      credStore._("add").call(1).arg(4, SESSION_TOKEN);
+      credStore._("add").call(1).arg(5, null);
+
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("startFlow").call(1).arg(1, "");
+      ui._("startFlow").call(1).arg(2, []);
+      ui._("verifyCodePrompt").callsLength(1);
+      ui._("verifyCodePrompt").call(1).arg(1, 3);
+      ui._("verify").callsLength(1);
+
+      // MockClient.
+      client._("discover").callsLength(1);
+      client._("discover").call(1).arg(1, PHONE_NUMBER);
+      client._("register").callsLength(1);
+      client._("smsMtVerify").callsLength(1);
+      client._("smsMtVerify").call(1).arg(1, SESSION_TOKEN);
+      client._("smsMtVerify").call(1).arg(2, PHONE_NUMBER);
+      client._("smsMtVerify").call(1).arg(3, true);
+      client._("verifyCode").callsLength(1);
+      client._("verifyCode").call(1).arg(1, SESSION_TOKEN);
+      client._("verifyCode").call(1).arg(2, {
+        verificationCode: VERIFICATION_CODE
+      });
+      client._("sign").callsLength(1);
+      client._("sign").call(1).arg(1, SESSION_TOKEN);
+      client._("sign").call(1).arg(2, CERTIFICATE_LIFETIME);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= No credentials - No icc - User MSISDN - External - KO -" +
+           " ERROR_INTERNAL_CANNOT_VERIFY_SELECTION =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore();
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient({
+    discoverResult: {
+      verificationMethods: ["other"],
+      verificationDetails: {
+        "other": {}
+      }
+    },
+    registerResult: {
+      msisdnSessionToken: _sessionToken
+    }
+  });
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_KO);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+      do_check_eq(aData.error, ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(1);
+      credStore._("getByMsisdn").call(1).arg(1, PHONE_NUMBER);
+      credStore._("add").callsLength(0);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("startFlow").call(1).arg(1, "");
+      ui._("startFlow").call(1).arg(2, []);
+      ui._("verifyCodePrompt").callsLength(0);
+      ui._("verify").callsLength(0);
+      ui._("error").callsLength(1);
+      ui._("error").call(1).arg(1, ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
+
+      // MockClient.
+      client._("discover").callsLength(1);
+      client._("discover").call(1).arg(1, PHONE_NUMBER);
+      client._("register").callsLength(0);
+      client._("smsMtVerify").callsLength(0);
+      client._("verifyCode").callsLength(0);
+      client._("sign").callsLength(0);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= No credentials - No icc - User MSISDN - External - KO -" +
+           " INTERNAL_INVALID_PROMPT_RESULT =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let ui = new MockUi({
+    startFlowResult: {}
+  });
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore();
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_KO);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+      do_check_eq(aData.error, ERROR_INTERNAL_INVALID_PROMPT_RESULT);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(0);
+      credStore._("add").callsLength(0);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("startFlow").call(1).arg(1, "");
+      ui._("startFlow").call(1).arg(2, []);
+      ui._("verifyCodePrompt").callsLength(0);
+      ui._("verify").callsLength(0);
+      ui._("error").callsLength(1);
+      ui._("error").call(1).arg(1, ERROR_INTERNAL_INVALID_PROMPT_RESULT);
+
+      // MockClient.
+      client._("discover").callsLength(0);
+      client._("register").callsLength(0);
+      client._("smsMtVerify").callsLength(0);
+      client._("verifyCode").callsLength(0);
+      client._("sign").callsLength(0);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= No credentials - No icc - User MSISDN - External - KO -" +
+           " ERROR_INVALID_ASSERTION =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore();
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient({
+    discoverResult: {
+      verificationMethods: ["sms/mt"],
+      verificationDetails: {
+        "sms/mt": {
+          mtSender: "123",
+          url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
+        }
+      }
+    },
+    signResult: {
+      cert: "aInvalidCert"
+    },
+    registerResult: {
+      msisdnSessionToken: _sessionToken
+    }
+  });
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_KO);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+      do_check_eq(aData.error, ERROR_INVALID_ASSERTION);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(1);
+      credStore._("getByMsisdn").call(1).arg(1, PHONE_NUMBER);
+      credStore._("add").callsLength(1);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("startFlow").call(1).arg(1, "");
+      ui._("startFlow").call(1).arg(2, []);
+      ui._("verifyCodePrompt").callsLength(1);
+      ui._("verifyCodePrompt").call(1).arg(1, 3);
+      ui._("verify").callsLength(1);
+      ui._("error").callsLength(1);
+      ui._("error").call(1).arg(1, ERROR_INVALID_ASSERTION);
+
+      // MockClient.
+      client._("discover").callsLength(1);
+      client._("discover").call(1).arg(1, PHONE_NUMBER);
+      client._("register").callsLength(1);
+      client._("smsMtVerify").callsLength(1);
+      client._("smsMtVerify").call(1).arg(1, _sessionToken);
+      client._("smsMtVerify").call(1).arg(2, PHONE_NUMBER);
+      client._("smsMtVerify").call(1).arg(3, true);
+      client._("verifyCode").callsLength(1);
+      client._("verifyCode").call(1).arg(1, _sessionToken);
+      client._("verifyCode").call(1).arg(2, {
+        verificationCode: VERIFICATION_CODE
+      });
+      client._("sign").callsLength(1);
+      client._("sign").call(1).arg(1, _sessionToken);
+      client._("sign").call(1).arg(2, CERTIFICATE_LIFETIME);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - Permission - OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: {
+      sessionToken: SESSION_TOKEN,
+      msisdn: PHONE_NUMBER,
+      origin: ORIGIN,
+      deviceIccIds: null
+    }
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+
+      removePermission(ORIGIN);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  addPermission(ORIGIN, Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {}
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - Prompt permission - OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: {
+      sessionToken: SESSION_TOKEN,
+      msisdn: PHONE_NUMBER,
+      origin: ORIGIN,
+      deviceIccIds: null
+    }
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+
+      removePermission(ORIGIN);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  addPermission(ORIGIN, Ci.nsIPermissionManager.PROMPT_ACTION);
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {}
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - Permission denied - OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: {
+      sessionToken: SESSION_TOKEN,
+      msisdn: PHONE_NUMBER,
+      origin: ORIGIN,
+      deviceIccIds: null
+    }
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_KO);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+      do_check_eq(aData.error, ERROR_PERMISSION_DENIED);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+
+      // MockUI.
+      ui._("startFlow").callsLength(0);
+      ui._("error").callsLength(1);
+      ui._("error").call(1).arg(1, ERROR_PERMISSION_DENIED);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  removePermission(ORIGIN);
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {}
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - SIM change/Same choice - " +
+           "OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let ui = new MockUi();
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: {
+      sessionToken: SESSION_TOKEN,
+      msisdn: PHONE_NUMBER,
+      origin: ORIGIN,
+      deviceIccIds: [ICC_ID]
+    }
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("add").callsLength(1);
+      credStore._("add").call(1).arg(1, undefined);
+      credStore._("add").call(1).arg(2, PHONE_NUMBER);
+      credStore._("add").call(1).arg(3, ORIGIN);
+      credStore._("add").call(1).arg(4, SESSION_TOKEN);
+      credStore._("add").call(1).arg(5, null);
+      credStore._("setDeviceIccIds").callsLength(1);
+      credStore._("setDeviceIccIds").call(1).arg(1, PHONE_NUMBER);
+      credStore._("setDeviceIccIds").call(1).arg(2, null);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("verifyCodePrompt").callsLength(0);
+      ui._("verify").callsLength(0);
+
+      // MockClient.
+      client._("discover").callsLength(0);
+      client._("register").callsLength(0);
+      client._("smsMtVerify").callsLength(0);
+      client._("verifyCode").callsLength(0);
+      client._("sign").callsLength(0);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {}
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - SIM change/Different choice - " +
+           "OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let existingCredentials = {
+    sessionToken: SESSION_TOKEN,
+    msisdn: PHONE_NUMBER,
+    origin: ORIGIN,
+    deviceIccIds: [ICC_ID]
+  };
+
+  let ui = new MockUi({
+    startFlowResult: {
+      phoneNumber: ANOTHER_PHONE_NUMBER
+    }
+  });
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: existingCredentials
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient({
+    verifyCodeResult: ANOTHER_PHONE_NUMBER,
+    discoverResult: {
+      verificationMethods: ["sms/mt"],
+      verificationDetails: {
+        "sms/mt": {
+          mtSender: "123",
+          url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
+        }
+      }
+    },
+    registerResult: {
+      msisdnSessionToken: _sessionToken
+    }
+  });
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(1);
+      credStore._("getByMsisdn").call(1).arg(1, ANOTHER_PHONE_NUMBER);
+      credStore._("add").callsLength(1);
+      credStore._("add").call(1).arg(1, undefined);
+      credStore._("add").call(1).arg(2, ANOTHER_PHONE_NUMBER);
+      credStore._("add").call(1).arg(3, ORIGIN);
+      credStore._("add").call(1).arg(4, _sessionToken);
+      credStore._("add").call(1).arg(5, null);
+      credStore._("setDeviceIccIds").callsLength(0);
+      credStore._("removeOrigin").callsLength(1);
+      credStore._("removeOrigin").call(1).arg(1, PHONE_NUMBER);
+      credStore._("removeOrigin").call(1).arg(2, ORIGIN);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("verifyCodePrompt").callsLength(1);
+      ui._("verify").callsLength(1);
+
+      // MockClient.
+      client._("discover").callsLength(1);
+      client._("register").callsLength(1);
+      client._("smsMtVerify").callsLength(1);
+      client._("verifyCode").callsLength(1);
+      client._("sign").callsLength(1);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {}
+    }
+  });
+});
+
+add_test(function() {
+  do_print("= Existing credentials - No Icc - forceSelection/same - " +
+           "OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let existingCredentials = {
+    sessionToken: _sessionToken,
+    msisdn: PHONE_NUMBER,
+    origin: ORIGIN,
+    deviceIccIds: []
+  };
+
+  let ui = new MockUi({
+    startFlowResult: {
+      phoneNumber: PHONE_NUMBER
+    }
+  });
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: existingCredentials
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient();
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(0);
+      credStore._("add").callsLength(1);
+      credStore._("add").call(1).arg(1, undefined);
+      credStore._("add").call(1).arg(2, PHONE_NUMBER);
+      credStore._("add").call(1).arg(3, ORIGIN);
+      credStore._("add").call(1).arg(4, _sessionToken);
+      credStore._("add").call(1).arg(5, null);
+      credStore._("setDeviceIccIds").callsLength(1);
+      credStore._("removeOrigin").callsLength(0);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("verifyCodePrompt").callsLength(0);
+      ui._("verify").callsLength(0);
+
+      // MockClient.
+      client._("discover").callsLength(0);
+      client._("register").callsLength(0);
+      client._("smsMtVerify").callsLength(0);
+      client._("verifyCode").callsLength(0);
+      client._("sign").callsLength(1);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {
+        forceSelection: true
+      }
+    }
+  });
+});
+add_test(function() {
+  do_print("= Existing credentials - No Icc - forceSelection/different - " +
+           "OK result =");
+
+  do_register_cleanup(cleanup);
+
+  do_test_pending();
+
+  let _sessionToken = Date.now();
+
+  let existingCredentials = {
+    sessionToken: SESSION_TOKEN,
+    msisdn: PHONE_NUMBER,
+    origin: ORIGIN,
+    deviceIccIds: []
+  };
+
+  let ui = new MockUi({
+    startFlowResult: {
+      phoneNumber: ANOTHER_PHONE_NUMBER
+    }
+  });
+  MobileIdentityManager.ui = ui;
+  let credStore = new MockCredStore({
+    getByOriginResult: existingCredentials
+  });
+  MobileIdentityManager.credStore = credStore;
+  let client = new MockClient({
+    verifyCodeResult: ANOTHER_PHONE_NUMBER,
+    discoverResult: {
+      verificationMethods: ["sms/mt"],
+      verificationDetails: {
+        "sms/mt": {
+          mtSender: "123",
+          url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
+        }
+      }
+    },
+    registerResult: {
+      msisdnSessionToken: _sessionToken
+    }
+  });
+  MobileIdentityManager.client = client;
+
+  let promiseId = Date.now();
+  let mm = {
+    sendAsyncMessage: function(aMsg, aData) {
+      do_print("sendAsyncMessage " + aMsg + " - " + JSON.stringify(aData));
+
+      // Check result.
+      do_check_eq(aMsg, GET_ASSERTION_RETURN_OK);
+      do_check_eq(typeof aData, "object");
+      do_check_eq(aData.promiseId, promiseId);
+
+      // Check spied calls.
+
+      // MockCredStore.
+      credStore._("getByOrigin").callsLength(1);
+      credStore._("getByOrigin").call(1).arg(1, ORIGIN);
+      credStore._("getByMsisdn").callsLength(1);
+      credStore._("getByMsisdn").call(1).arg(1, ANOTHER_PHONE_NUMBER);
+      credStore._("add").callsLength(1);
+      credStore._("add").call(1).arg(1, undefined);
+      credStore._("add").call(1).arg(2, ANOTHER_PHONE_NUMBER);
+      credStore._("add").call(1).arg(3, ORIGIN);
+      credStore._("add").call(1).arg(4, _sessionToken);
+      credStore._("add").call(1).arg(5, null);
+      credStore._("setDeviceIccIds").callsLength(0);
+      credStore._("removeOrigin").callsLength(1);
+      credStore._("removeOrigin").call(1).arg(1, PHONE_NUMBER);
+      credStore._("removeOrigin").call(1).arg(2, ORIGIN);
+
+      // MockUI.
+      ui._("startFlow").callsLength(1);
+      ui._("verifyCodePrompt").callsLength(1);
+      ui._("verify").callsLength(1);
+
+      // MockClient.
+      client._("discover").callsLength(1);
+      client._("register").callsLength(1);
+      client._("smsMtVerify").callsLength(1);
+      client._("verifyCode").callsLength(1);
+      client._("sign").callsLength(1);
+
+      do_test_finished();
+      run_next_test();
+    }
+  };
+
+  MobileIdentityManager.receiveMessage({
+    name: GET_ASSERTION_IPC_MSG,
+    principal: PRINCIPAL,
+    target: mm,
+    json: {
+      promiseId: promiseId,
+      options: {
+        forceSelection: true
+      }
+    }
+  });
+});
new file mode 100644
--- /dev/null
+++ b/services/mobileid/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = head.js
+tail =
+
+[test_mobileid_manager.js]