/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/util.js");

let identity = new IdentityManager();

function run_test() {
  initTestLogging("Trace");
  Log.repository.getLogger("Sync.Identity").level = Log.Level.Trace;

  run_next_test();
}

add_test(function test_username_from_account() {
  _("Ensure usernameFromAccount works properly.");

  do_check_eq(identity.usernameFromAccount(null), null);
  do_check_eq(identity.usernameFromAccount("user"), "user");
  do_check_eq(identity.usernameFromAccount("User"), "user");
  do_check_eq(identity.usernameFromAccount("john@doe.com"),
                                           "7wohs32cngzuqt466q3ge7indszva4of");

  run_next_test();
});

add_test(function test_account_username() {
  _("Ensure the account and username attributes work properly.");

  _("Verify initial state");
  do_check_eq(Svc.Prefs.get("account"), undefined);
  do_check_eq(Svc.Prefs.get("username"), undefined);
  do_check_eq(identity.account, null);
  do_check_eq(identity.username, null);

  _("The 'username' attribute is normalized to lower case, updates preferences and identities.");
  identity.username = "TarZan";
  do_check_eq(identity.username, "tarzan");
  do_check_eq(Svc.Prefs.get("username"), "tarzan");
  do_check_eq(identity.username, "tarzan");

  _("If not set, the 'account attribute' falls back to the username for backwards compatibility.");
  do_check_eq(identity.account, "tarzan");

  _("Setting 'username' to a non-truthy value resets the pref.");
  identity.username = null;
  do_check_eq(identity.username, null);
  do_check_eq(identity.account, null);
  const default_marker = {};
  do_check_eq(Svc.Prefs.get("username", default_marker), default_marker);
  do_check_eq(identity.username, null);

  _("The 'account' attribute will set the 'username' if it doesn't contain characters that aren't allowed in the username.");
  identity.account = "johndoe";
  do_check_eq(identity.account, "johndoe");
  do_check_eq(identity.username, "johndoe");
  do_check_eq(Svc.Prefs.get("username"), "johndoe");
  do_check_eq(identity.username, "johndoe");

  _("If 'account' contains disallowed characters such as @, 'username' will the base32 encoded SHA1 hash of 'account'");
  identity.account = "John@Doe.com";
  do_check_eq(identity.account, "john@doe.com");
  do_check_eq(identity.username, "7wohs32cngzuqt466q3ge7indszva4of");

  _("Setting 'account' to a non-truthy value resets the pref.");
  identity.account = null;
  do_check_eq(identity.account, null);
  do_check_eq(Svc.Prefs.get("account", default_marker), default_marker);
  do_check_eq(identity.username, null);
  do_check_eq(Svc.Prefs.get("username", default_marker), default_marker);

  Svc.Prefs.resetBranch("");
  run_next_test();
});

add_test(function test_basic_password() {
  _("Ensure basic password setting works as expected.");

  identity.account = null;
  do_check_eq(identity.currentAuthState, LOGIN_FAILED_NO_USERNAME);
  let thrown = false;
  try {
    identity.basicPassword = "foobar";
  } catch (ex) {
    thrown = true;
  }

  do_check_true(thrown);
  thrown = false;

  identity.account = "johndoe";
  do_check_eq(identity.currentAuthState, LOGIN_FAILED_NO_PASSWORD);
  identity.basicPassword = "password";
  do_check_eq(identity.basicPassword, "password");
  do_check_eq(identity.currentAuthState, LOGIN_FAILED_NO_PASSPHRASE);
  do_check_true(identity.hasBasicCredentials());

  identity.account = null;

  run_next_test();
});

add_test(function test_basic_password_persistence() {
  _("Ensure credentials are saved and restored to the login manager properly.");

  // Just in case.
  identity.account = null;
  identity.deleteSyncCredentials();

  identity.account = "janesmith";
  identity.basicPassword = "ilovejohn";
  identity.persistCredentials();

  let im1 = new IdentityManager();
  do_check_eq(im1._basicPassword, null);
  do_check_eq(im1.username, "janesmith");
  do_check_eq(im1.basicPassword, "ilovejohn");

  let im2 = new IdentityManager();
  do_check_eq(im2._basicPassword, null);

  _("Now remove the password and ensure it is deleted from storage.");
  identity.basicPassword = null;
  identity.persistCredentials(); // This should nuke from storage.
  do_check_eq(im2.basicPassword, null);

  _("Ensure that retrieving an unset but unpersisted removal returns null.");
  identity.account = "janesmith";
  identity.basicPassword = "myotherpassword";
  identity.persistCredentials();

  identity.basicPassword = null;
  do_check_eq(identity.basicPassword, null);

  // Reset for next test.
  identity.account = null;
  identity.persistCredentials();

  run_next_test();
});

add_test(function test_sync_key() {
  _("Ensure Sync Key works as advertised.");

  _("Ensure setting a Sync Key before an account throws.");
  let thrown = false;
  try {
    identity.syncKey = "blahblah";
  } catch (ex) {
    thrown = true;
  }
  do_check_true(thrown);
  thrown = false;

  identity.account = "johnsmith";
  identity.basicPassword = "johnsmithpw";

  do_check_eq(identity.syncKey, null);
  do_check_eq(identity.syncKeyBundle, null);

  _("An invalid Sync Key is silently accepted for historical reasons.");
  identity.syncKey = "synckey";
  do_check_eq(identity.syncKey, "synckey");

  _("But the SyncKeyBundle should not be created from bad keys.");
  do_check_eq(identity.syncKeyBundle, null);

  let syncKey = Utils.generatePassphrase();
  identity.syncKey = syncKey;
  do_check_eq(identity.syncKey, syncKey);
  do_check_neq(identity.syncKeyBundle, null);

  let im = new IdentityManager();
  im.account = "pseudojohn";
  do_check_eq(im.syncKey, null);
  do_check_eq(im.syncKeyBundle, null);

  identity.account = null;

  run_next_test();
});

add_test(function test_sync_key_changes() {
  _("Ensure changes to Sync Key have appropriate side-effects.");

  let im = new IdentityManager();
  let sk1 = Utils.generatePassphrase();
  let sk2 = Utils.generatePassphrase();

  im.account = "johndoe";
  do_check_eq(im.syncKey, null);
  do_check_eq(im.syncKeyBundle, null);

  im.syncKey = sk1;
  do_check_neq(im.syncKeyBundle, null);

  let ek1 = im.syncKeyBundle.encryptionKeyB64;
  let hk1 = im.syncKeyBundle.hmacKeyB64;

  // Change the Sync Key and ensure the Sync Key Bundle is updated.
  im.syncKey = sk2;
  let ek2 = im.syncKeyBundle.encryptionKeyB64;
  let hk2 = im.syncKeyBundle.hmacKeyB64;

  do_check_neq(ek1, ek2);
  do_check_neq(hk1, hk2);

  im.account = null;

  run_next_test();
});

add_test(function test_current_auth_state() {
  _("Ensure current auth state is reported properly.");

  let im = new IdentityManager();
  do_check_eq(im.currentAuthState, LOGIN_FAILED_NO_USERNAME);

  im.account = "johndoe";
  do_check_eq(im.currentAuthState, LOGIN_FAILED_NO_PASSWORD);

  im.basicPassword = "ilovejane";
  do_check_eq(im.currentAuthState, LOGIN_FAILED_NO_PASSPHRASE);

  im.syncKey = "foobar";
  do_check_eq(im.currentAuthState, LOGIN_FAILED_INVALID_PASSPHRASE);

  im.syncKey = null;
  do_check_eq(im.currentAuthState, LOGIN_FAILED_NO_PASSPHRASE);

  im.syncKey = Utils.generatePassphrase();
  do_check_eq(im.currentAuthState, STATUS_OK);

  im.account = null;

  run_next_test();
});

add_test(function test_sync_key_persistence() {
  _("Ensure Sync Key persistence works as expected.");

  identity.account = "pseudojohn";
  identity.password = "supersecret";

  let syncKey = Utils.generatePassphrase();
  identity.syncKey = syncKey;

  identity.persistCredentials();

  let im = new IdentityManager();
  im.account = "pseudojohn";
  do_check_eq(im.syncKey, syncKey);
  do_check_neq(im.syncKeyBundle, null);

  let kb1 = identity.syncKeyBundle;
  let kb2 = im.syncKeyBundle;

  do_check_eq(kb1.encryptionKeyB64, kb2.encryptionKeyB64);
  do_check_eq(kb1.hmacKeyB64, kb2.hmacKeyB64);

  identity.account = null;
  identity.persistCredentials();

  let im2 = new IdentityManager();
  im2.account = "pseudojohn";
  do_check_eq(im2.syncKey, null);

  im2.account = null;

  _("Ensure deleted but not persisted value is retrieved.");
  identity.account = "someoneelse";
  identity.syncKey = Utils.generatePassphrase();
  identity.persistCredentials();
  identity.syncKey = null;
  do_check_eq(identity.syncKey, null);

  // Clean up.
  identity.account = null;
  identity.persistCredentials();

  run_next_test();
});
