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

"use strict";

const { getFxAccountsSingleton } = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccounts.sys.mjs"
);
const fxAccounts = getFxAccountsSingleton();

const { ON_NEW_DEVICE_ID, PREF_ACCOUNT_ROOT } = ChromeUtils.import(
  "resource://gre/modules/FxAccountsCommon.js"
);

function promiseObserved(topic) {
  return new Promise(res => {
    Services.obs.addObserver(res, topic);
  });
}

_("Misc tests for FxAccounts.device");

fxAccounts.device._checkRemoteCommandsUpdateNeeded = async () => false;

add_test(function test_default_device_name() {
  // Note that head_helpers overrides getDefaultLocalName - this test is
  // really just to ensure the actual implementation is sane - we can't
  // really check the value it uses is correct.
  // We are just hoping to avoid a repeat of bug 1369285.
  let def = fxAccounts.device.getDefaultLocalName(); // make sure it doesn't throw.
  _("default value is " + def);
  ok(!!def.length);

  // This is obviously tied to the implementation, but we want early warning
  // if any of these things fail.
  // We really want one of these 2 to provide a value.
  let hostname = Services.sysinfo.get("device") || Services.dns.myHostName;
  _("hostname is " + hostname);
  ok(!!hostname.length);
  // the hostname should be in the default.
  ok(def.includes(hostname));
  // We expect the following to work as a fallback to the above.
  let fallback = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
    Ci.nsIHttpProtocolHandler
  ).oscpu;
  _("UA fallback is " + fallback);
  ok(!!fallback.length);
  // the fallback should not be in the default
  ok(!def.includes(fallback));

  run_next_test();
});

add_test(function test_migration() {
  Services.prefs.clearUserPref("identity.fxaccounts.account.device.name");
  Services.prefs.setStringPref("services.sync.client.name", "my client name");
  // calling getLocalName() should move the name to the new pref and reset the old.
  equal(fxAccounts.device.getLocalName(), "my client name");
  equal(
    Services.prefs.getStringPref("identity.fxaccounts.account.device.name"),
    "my client name"
  );
  ok(!Services.prefs.prefHasUserValue("services.sync.client.name"));
  run_next_test();
});

add_test(function test_migration_set_before_get() {
  Services.prefs.setStringPref("services.sync.client.name", "old client name");
  fxAccounts.device.setLocalName("new client name");
  equal(fxAccounts.device.getLocalName(), "new client name");
  run_next_test();
});

add_task(async function test_reset() {
  // We don't test the client name specifically here because the client name
  // is set as part of signing the user in via the attempt to register the
  // device.
  const testPref = PREF_ACCOUNT_ROOT + "test-pref";
  Services.prefs.setStringPref(testPref, "whatever");
  let credentials = {
    email: "foo@example.com",
    uid: "1234@lcip.org",
    sessionToken: "dead",
    verified: true,
    ...MOCK_ACCOUNT_KEYS,
  };
  // FxA will try to register its device record in the background after signin.
  const registerDevice = sinon
    .stub(fxAccounts._internal.fxAccountsClient, "registerDevice")
    .callsFake(async () => {
      return { id: "foo" };
    });
  await fxAccounts._internal.setSignedInUser(credentials);
  // wait for device registration to complete.
  await promiseObserved(ON_NEW_DEVICE_ID);
  ok(!Services.prefs.prefHasUserValue(testPref));
  // signing the user out should reset the name pref.
  const namePref = PREF_ACCOUNT_ROOT + "device.name";
  ok(Services.prefs.prefHasUserValue(namePref));
  await fxAccounts.signOut(/* localOnly = */ true);
  ok(!Services.prefs.prefHasUserValue(namePref));
  registerDevice.restore();
});

add_task(async function test_name_sanitization() {
  fxAccounts.device.setLocalName("emoji is valid \u2665");
  Assert.equal(fxAccounts.device.getLocalName(), "emoji is valid \u2665");

  let invalid = "x\uFFFD\n\r\t" + "x".repeat(255);
  let sanitized = "x\uFFFD\uFFFD\uFFFD\uFFFD" + "x".repeat(250); // 255 total.

  // If the pref already has the invalid value we still get the valid one back.
  Services.prefs.setStringPref(
    "identity.fxaccounts.account.device.name",
    invalid
  );
  Assert.equal(fxAccounts.device.getLocalName(), sanitized);

  // But if we explicitly set it to an invalid name, the sanitized value ends
  // up in the pref.
  fxAccounts.device.setLocalName(invalid);
  Assert.equal(fxAccounts.device.getLocalName(), sanitized);
  Assert.equal(
    Services.prefs.getStringPref("identity.fxaccounts.account.device.name"),
    sanitized
  );
});
