/**
 * Test LoginBreaches.getPotentialBreachesByLoginGUID
 */

"use strict";

const { RemoteSettings } = ChromeUtils.importESModule(
  "resource://services-settings/remote-settings.sys.mjs"
);

// Initializing BrowserGlue requires a profile on Windows.
do_get_profile();

const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
  Ci.nsIObserver
);

ChromeUtils.defineESModuleGetters(this, {
  LoginBreaches: "resource:///modules/LoginBreaches.sys.mjs",
});

const TEST_BREACHES = [
  {
    AddedDate: "2018-12-20T23:56:26Z",
    BreachDate: "2018-12-16",
    Domain: "breached.com",
    Name: "Breached",
    PwnCount: 1643100,
    DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
    _status: "synced",
    id: "047940fe-d2fd-4314-b636-b4a952ee0043",
    last_modified: "1541615610052",
    schema: "1541615609018",
  },
  {
    AddedDate: "2018-12-20T23:56:26Z",
    BreachDate: "2018-12-16",
    Domain: "breached-subdomain.host.com",
    Name: "Only a Sub-Domain was Breached",
    PwnCount: 2754200,
    DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
    _status: "synced",
    id: "047940fe-d2fd-4314-b636-b4a952ee0044",
    last_modified: "1541615610052",
    schema: "1541615609018",
  },
  {
    AddedDate: "2018-12-20T23:56:26Z",
    BreachDate: "2018-12-16",
    Domain: "breached-site-without-passwords.com",
    Name: "Breached Site without passwords",
    PwnCount: 987654,
    DataClasses: ["Email addresses", "Usernames", "IP addresses"],
    _status: "synced",
    id: "047940fe-d2fd-4314-b636-b4a952ee0045",
    last_modified: "1541615610052",
    schema: "1541615609018",
  },
];

const CRASHING_URI_LOGIN = LoginTestUtils.testData.formLogin({
  origin: "chrome://grwatcher",
  formActionOrigin: "https://www.example.com",
  username: "username",
  password: "password",
  timePasswordChanged: new Date("2018-12-15").getTime(),
});
const NOT_BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
  origin: "https://www.example.com",
  formActionOrigin: "https://www.example.com",
  username: "username",
  password: "password",
  timePasswordChanged: new Date("2018-12-15").getTime(),
});
const BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
  origin: "https://www.breached.com",
  formActionOrigin: "https://www.breached.com",
  username: "username",
  password: "password",
  timePasswordChanged: new Date("2018-12-15").getTime(),
});
const NOT_BREACHED_SUBDOMAIN_LOGIN = LoginTestUtils.testData.formLogin({
  origin: "https://not-breached-subdomain.host.com",
  formActionOrigin: "https://not-breached-subdomain.host.com",
  username: "username",
  password: "password",
});
const BREACHED_SUBDOMAIN_LOGIN = LoginTestUtils.testData.formLogin({
  origin: "https://breached-subdomain.host.com",
  formActionOrigin: "https://breached-subdomain.host.com",
  username: "username",
  password: "password",
  timePasswordChanged: new Date("2018-12-15").getTime(),
});
const LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS =
  LoginTestUtils.testData.formLogin({
    origin: "https://breached-site-without-passwords.com",
    formActionOrigin: "https://breached-site-without-passwords.com",
    username: "username",
    password: "password",
    timePasswordChanged: new Date("2018-12-15").getTime(),
  });
const LOGIN_WITH_NON_STANDARD_URI = LoginTestUtils.testData.formLogin({
  origin: "someApp://random/path/to/login",
  formActionOrigin: "someApp://random/path/to/login",
  username: "username",
  password: "password",
  timePasswordChanged: new Date("2018-12-15").getTime(),
});

add_task(async function test_notBreachedLogin() {
  await Services.logins.addLoginAsync(NOT_BREACHED_LOGIN);
  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [NOT_BREACHED_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    0,
    "Should be 0 breached logins."
  );
});

add_task(async function test_breachedLogin() {
  await Services.logins.addLoginAsync(BREACHED_LOGIN);
  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [NOT_BREACHED_LOGIN, BREACHED_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Should be 1 breached login: " + BREACHED_LOGIN.origin
  );
  Assert.strictEqual(
    breachesByLoginGUID.get(BREACHED_LOGIN.guid).breachAlertURL,
    "https://monitor.firefox.com/breach-details/Breached?utm_source=firefox-desktop&utm_medium=referral&utm_campaign=about-logins&utm_content=about-logins",
    "Breach alert link should be equal to the breachAlertURL"
  );
});

add_task(async function test_breachedLoginAfterCrashingUriLogin() {
  await Services.logins.addLoginAsync(CRASHING_URI_LOGIN);

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [CRASHING_URI_LOGIN, BREACHED_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Should be 1 breached login: " + BREACHED_LOGIN.origin
  );
  Assert.strictEqual(
    breachesByLoginGUID.get(BREACHED_LOGIN.guid).breachAlertURL,
    "https://monitor.firefox.com/breach-details/Breached?utm_source=firefox-desktop&utm_medium=referral&utm_campaign=about-logins&utm_content=about-logins",
    "Breach alert link should be equal to the breachAlertURL"
  );
});

add_task(async function test_notBreachedSubdomain() {
  await Services.logins.addLoginAsync(NOT_BREACHED_SUBDOMAIN_LOGIN);

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [NOT_BREACHED_LOGIN, NOT_BREACHED_SUBDOMAIN_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    0,
    "Should be 0 breached logins."
  );
});

add_task(async function test_breachedSubdomain() {
  await Services.logins.addLoginAsync(BREACHED_SUBDOMAIN_LOGIN);

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [NOT_BREACHED_SUBDOMAIN_LOGIN, BREACHED_SUBDOMAIN_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Should be 1 breached login: " + BREACHED_SUBDOMAIN_LOGIN.origin
  );
});

add_task(async function test_breachedSiteWithoutPasswords() {
  await Services.logins.addLoginAsync(
    LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS
  );

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    0,
    "Should be 0 breached login: " +
      LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
  );
});

add_task(async function test_breachAlertHiddenAfterDismissal() {
  BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";

  await Services.logins.initializationPromise;
  const storageJSON = Services.logins.wrappedJSObject._storage;

  storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID.size,
    0,
    "Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
  );

  info("Clear login storage");
  Services.logins.removeAllUserFacingLogins();

  const breachesByLoginGUID2 =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
      TEST_BREACHES
    );
  Assert.strictEqual(
    breachesByLoginGUID2.size,
    1,
    "Breached login should re-appear after clearing storage: " +
      BREACHED_LOGIN.origin
  );
});

add_task(async function test_newBreachAfterDismissal() {
  TEST_BREACHES[0].AddedDate = new Date().toISOString();

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
      TEST_BREACHES
    );

  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Should be 1 breached login after new breach following the dismissal of a previous breach: " +
      BREACHED_LOGIN.origin
  );
});

add_task(async function test_ExceptionsThrownByNonStandardURIsAreCaught() {
  await Services.logins.addLoginAsync(LOGIN_WITH_NON_STANDARD_URI);

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID(
      [LOGIN_WITH_NON_STANDARD_URI, BREACHED_LOGIN],
      TEST_BREACHES
    );

  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Exceptions thrown by logins with non-standard URIs should be caught."
  );
});

add_task(async function test_setBreachesFromRemoteSettingsSync() {
  const login = NOT_BREACHED_SUBDOMAIN_LOGIN;
  const nowExampleIsInBreachedRecords = [
    {
      AddedDate: "2018-12-20T23:56:26Z",
      BreachDate: "2018-12-16",
      Domain: "not-breached-subdomain.host.com",
      Name: "not-breached-subdomain.host.com is now breached!",
      PwnCount: 1643100,
      DataClasses: [
        "Email addresses",
        "Usernames",
        "Passwords",
        "IP addresses",
      ],
      _status: "synced",
      id: "047940fe-d2fd-4314-b636-b4a952ee0044",
      last_modified: "1541615610052",
      schema: "1541615609018",
    },
  ];
  async function emitSync() {
    await RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).emit(
      "sync",
      { data: { current: nowExampleIsInBreachedRecords } }
    );
  }

  const beforeSyncBreachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID([login]);
  Assert.strictEqual(
    beforeSyncBreachesByLoginGUID.size,
    0,
    "Should be 0 breached login before not-breached-subdomain.host.com is added to fxmonitor-breaches collection and synced: "
  );
  gBrowserGlue.observe(null, "browser-glue-test", "add-breaches-sync-handler");
  const db = RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).db;
  await db.importChanges({}, Date.now(), [nowExampleIsInBreachedRecords[0]]);
  await emitSync();

  const breachesByLoginGUID =
    await LoginBreaches.getPotentialBreachesByLoginGUID([login]);
  Assert.strictEqual(
    breachesByLoginGUID.size,
    1,
    "Should be 1 breached login after not-breached-subdomain.host.com is added to fxmonitor-breaches collection and synced: "
  );
});
