// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";

// Tests various aspects of the nsISecretDecoderRing implementation.

do_get_profile();

let gSetPasswordShownCount = 0;

// Mock implementation of nsITokenPasswordDialogs.
const gTokenPasswordDialogs = {
  setPassword: (ctx, tokenName, canceled) => {
    gSetPasswordShownCount++;
    do_print(`setPassword() called; shown ${gSetPasswordShownCount} times`);
    do_print(`tokenName: ${tokenName}`);
    canceled.value = false;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsITokenPasswordDialogs])
};

function run_test() {
  // We have to set a password and login before we attempt to encrypt anything.
  // In particular, failing to do so will cause the Encrypt() implementation to
  // pop up a dialog asking for a password to be set. This won't work in the
  // xpcshell environment and will lead to an assertion.
  loginToDBWithDefaultPassword();

  let sdr = Cc["@mozilla.org/security/sdr;1"]
              .getService(Ci.nsISecretDecoderRing);

  // Test valid inputs for encryptString() and decryptString().
  let inputs = [
    "",
    " ", // First printable latin1 character (code point 32).
    "foo",
    "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?",
    "¡äöüÿ", // Misc + last printable latin1 character (code point 255).
    "aaa 一二三", // Includes Unicode with code points outside [0, 255].
  ];
  for (let input of inputs) {
    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                      .createInstance(Ci.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";

    let convertedInput = converter.ConvertFromUnicode(input);
    convertedInput += converter.Finish();

    let encrypted = sdr.encryptString(convertedInput);

    notEqual(convertedInput, encrypted,
             "Encrypted input should not just be the input itself");

    try {
      atob(encrypted);
    } catch (e) {
      ok(false, `encryptString() should have returned Base64: ${e}`);
    }

    equal(convertedInput, sdr.decryptString(encrypted),
          "decryptString(encryptString(input)) should return input");
  }

  // Test invalid inputs for decryptString().
  throws(() => sdr.decryptString("*"), /NS_ERROR_ILLEGAL_VALUE/,
         "decryptString() should throw if given non-Base64 input");

  // Test calling changePassword() pops up the appropriate dialog.
  // Note: On Android, nsITokenPasswordDialogs is apparently not implemented,
  //       which also seems to prevent us from mocking out the interface.
  if (AppConstants.platform != "android") {
    let tokenPasswordDialogsCID =
      MockRegistrar.register("@mozilla.org/nsTokenPasswordDialogs;1",
                             gTokenPasswordDialogs);
    do_register_cleanup(() => {
      MockRegistrar.unregister(tokenPasswordDialogsCID);
    });

    equal(gSetPasswordShownCount, 0,
          "changePassword() dialog should have been shown zero times");
    sdr.changePassword();
    equal(gSetPasswordShownCount, 1,
          "changePassword() dialog should have been shown exactly once");
  }
}
