/**
 * Test LoginHelper.dedupeLogins
 */

"use strict";

const DOMAIN1_HTTP_TO_HTTP_U1_P1 = TestData.formLogin({
  timePasswordChanged: 3000,
  timeLastUsed: 2000,
});
const DOMAIN1_HTTP_TO_HTTP_U1_P2 = TestData.formLogin({
  password: "password two",
});
const DOMAIN2_HTTP_TO_HTTP_U2_P2 = TestData.formLogin({
  origin: "http://www4.example.com",
  formActionOrigin: "http://www4.example.com",
  password: "password two",
  username: "username two",
});

const DOMAIN1_HTTPS_TO_HTTP_U1_P1 = TestData.formLogin({
  formActionOrigin: "http://www.example.com",
  origin: "https://www3.example.com",
  timePasswordChanged: 4000,
  timeLastUsed: 1000,
});
const DOMAIN1_HTTPS_TO_HTTPS_U1_P1 = TestData.formLogin({
  formActionOrigin: "https://www.example.com",
  origin: "https://www3.example.com",
  timePasswordChanged: 4000,
  timeLastUsed: 1000,
});

const DOMAIN1_HTTPS_TO_EMPTY_U1_P1 = TestData.formLogin({
  formActionOrigin: "",
  origin: "https://www3.example.com",
});
const DOMAIN1_HTTPS_TO_EMPTYU_P1 = TestData.formLogin({
  origin: "https://www3.example.com",
  username: "",
});
const DOMAIN1_HTTP_AUTH = TestData.authLogin({
  origin: "http://www3.example.com",
});
const DOMAIN1_HTTPS_AUTH = TestData.authLogin({
  origin: "https://www3.example.com",
});
const DOMAIN1_HTTPS_LOGIN = TestData.formLogin({
  origin: "https://www3.example.com",
  formActionOrigin: "https://www3.example.com",
});
const DOMAIN1_HTTP_LOGIN = TestData.formLogin({
  origin: "http://www3.example.com",
  formActionOrigin: "http://www3.example.com",
});
const DOMAIN1_HTTPS_NONSTANDARD_PORT1 = TestData.formLogin({
  origin: "https://www3.example.com:8001",
  formActionOrigin: "https://www3.example.com:8001",
});
const DOMAIN1_HTTPS_NONSTANDARD_PORT2 = TestData.formLogin({
  origin: "https://www3.example.com:8008",
  formActionOrigin: "https://www3.example.com:8008",
});
const DOMAIN2_HTTPS_LOGIN = TestData.formLogin({
  origin: "https://www4.example.com",
  formActionOrigin: "https://www4.example.com",
});
const DOMAIN2_HTTPS_LOGIN_NEWER = TestData.formLogin({
  origin: "https://www4.example.com",
  formActionOrigin: "https://www4.example.com",
  timePasswordChanged: 4000,
  timeLastUsed: 4000,
});
const DOMAIN2_HTTPS_TO_HTTPS_U2_P2 = TestData.formLogin({
  origin: "https://www4.example.com",
  formActionOrigin: "https://www4.example.com",
  password: "password two",
  username: "username two",
});

add_task(function test_dedupeLogins() {
  // [description, expectedOutput, dedupe arg. 0, dedupe arg 1, ...]
  let testcases = [
    [
      "exact dupes",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      [], // force no resolveBy logic to test behavior of preferring the first..
    ],
    [
      "default uniqueKeys is un + pw",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P2],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P2],
      undefined,
      [],
    ],
    [
      "same usernames, different passwords, dedupe username only",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P2],
      ["username"],
      [],
    ],
    [
      "same un+pw, different scheme",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      [],
    ],
    [
      "same un+pw, different scheme, reverse order",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      [],
    ],
    [
      "same un+pw, different scheme, include origin",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      ["origin", "username", "password"],
      [],
    ],
    [
      "empty username is not deduped with non-empty",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_EMPTYU_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_EMPTYU_P1],
      undefined,
      [],
    ],
    [
      "empty username is deduped with same passwords",
      [DOMAIN1_HTTPS_TO_EMPTYU_P1],
      [DOMAIN1_HTTPS_TO_EMPTYU_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      ["password"],
      [],
    ],
    [
      "mix of form and HTTP auth",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTP_AUTH],
      undefined,
      [],
    ],
  ];

  for (let tc of testcases) {
    let description = tc.shift();
    let expected = tc.shift();
    let actual = LoginHelper.dedupeLogins(...tc);
    Assert.strictEqual(actual.length, expected.length, `Check: ${description}`);
    for (let [i, login] of expected.entries()) {
      Assert.strictEqual(actual[i], login, `Check index ${i}`);
    }
  }
});

add_task(async function test_dedupeLogins_resolveBy() {
  Assert.greater(
    DOMAIN1_HTTP_TO_HTTP_U1_P1.timeLastUsed,
    DOMAIN1_HTTPS_TO_HTTP_U1_P1.timeLastUsed,
    "Sanity check timeLastUsed difference"
  );
  Assert.less(
    DOMAIN1_HTTP_TO_HTTP_U1_P1.timePasswordChanged,
    DOMAIN1_HTTPS_TO_HTTP_U1_P1.timePasswordChanged,
    "Sanity check timePasswordChanged difference"
  );
  Assert.less(
    DOMAIN1_HTTPS_LOGIN.timePasswordChanged,
    DOMAIN2_HTTPS_LOGIN_NEWER.timePasswordChanged,
    "Sanity check timePasswordChanged difference"
  );

  let testcases = [
    [
      "default resolveBy is timeLastUsed",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
    ],
    [
      "default resolveBy is timeLastUsed, reversed input",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
    ],
    [
      "resolveBy timeLastUsed + timePasswordChanged",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["timeLastUsed", "timePasswordChanged"],
    ],
    [
      "resolveBy timeLastUsed + timePasswordChanged, reversed input",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      ["timeLastUsed", "timePasswordChanged"],
    ],
    [
      "resolveBy timePasswordChanged",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["timePasswordChanged"],
    ],
    [
      "resolveBy timePasswordChanged, reversed",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      ["timePasswordChanged"],
    ],
    [
      "resolveBy timePasswordChanged + timeLastUsed",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["timePasswordChanged", "timeLastUsed"],
    ],
    [
      "resolveBy timePasswordChanged + timeLastUsed, reversed",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      ["timePasswordChanged", "timeLastUsed"],
    ],
    [
      "resolveBy scheme + timePasswordChanged, prefer HTTP",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
      DOMAIN1_HTTP_TO_HTTP_U1_P1.origin,
    ],
    [
      "resolveBy scheme + timePasswordChanged, prefer HTTP, reversed input",
      [DOMAIN1_HTTP_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
      DOMAIN1_HTTP_TO_HTTP_U1_P1.origin,
    ],
    [
      "resolveBy scheme + timePasswordChanged, prefer HTTPS",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
      DOMAIN1_HTTPS_TO_HTTP_U1_P1.origin,
    ],
    [
      "resolveBy scheme + timePasswordChanged, prefer HTTPS, reversed input",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
      DOMAIN1_HTTPS_TO_HTTP_U1_P1.origin,
    ],
    [
      "resolveBy scheme HTTP auth",
      [DOMAIN1_HTTPS_AUTH],
      [DOMAIN1_HTTP_AUTH, DOMAIN1_HTTPS_AUTH],
      undefined,
      ["scheme"],
      DOMAIN1_HTTPS_AUTH.origin,
    ],
    [
      "resolveBy scheme HTTP auth, reversed input",
      [DOMAIN1_HTTPS_AUTH],
      [DOMAIN1_HTTPS_AUTH, DOMAIN1_HTTP_AUTH],
      undefined,
      ["scheme"],
      DOMAIN1_HTTPS_AUTH.origin,
    ],
    [
      "resolveBy scheme, empty form submit URL",
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1],
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_EMPTY_U1_P1],
      undefined,
      ["scheme"],
      DOMAIN1_HTTPS_TO_HTTP_U1_P1.origin,
    ],
    [
      "resolveBy subdomain, different subdomains, same login, subdomain1 preferred",
      [DOMAIN1_HTTPS_LOGIN],
      [DOMAIN1_HTTPS_LOGIN, DOMAIN2_HTTPS_LOGIN],
      undefined,
      ["subdomain"],
      DOMAIN1_HTTPS_LOGIN.origin,
    ],
    [
      "resolveBy subdomain, different subdomains, same login, subdomain2 preferred",
      [DOMAIN2_HTTPS_LOGIN],
      [DOMAIN1_HTTPS_LOGIN, DOMAIN2_HTTPS_LOGIN],
      undefined,
      ["subdomain"],
      DOMAIN2_HTTPS_LOGIN.origin,
    ],
    [
      "resolveBy subdomain+timePasswordChanged, different subdomains, same login, subdomain1 preferred",
      [DOMAIN1_HTTPS_LOGIN],
      [DOMAIN1_HTTPS_LOGIN, DOMAIN2_HTTPS_LOGIN_NEWER],
      undefined,
      ["subdomain", "timePasswordChanged"],
      DOMAIN1_HTTPS_LOGIN.origin,
    ],
    [
      "resolveBy subdomain, same subdomain, different schemes",
      [DOMAIN1_HTTPS_LOGIN],
      [DOMAIN1_HTTPS_LOGIN, DOMAIN1_HTTP_LOGIN],
      undefined,
      ["subdomain"],
      DOMAIN1_HTTPS_LOGIN.origin,
    ],
    [
      "resolveBy subdomain, same subdomain, different ports",
      [DOMAIN1_HTTPS_LOGIN],
      [
        DOMAIN1_HTTPS_LOGIN,
        DOMAIN1_HTTPS_NONSTANDARD_PORT1,
        DOMAIN1_HTTPS_NONSTANDARD_PORT2,
      ],
      undefined,
      ["subdomain"],
      DOMAIN1_HTTPS_LOGIN.origin,
    ],
    [
      "resolveBy subdomain, same subdomain, different schemes, different ports",
      [DOMAIN1_HTTPS_LOGIN],
      [
        DOMAIN1_HTTPS_LOGIN,
        DOMAIN1_HTTPS_NONSTANDARD_PORT1,
        DOMAIN1_HTTPS_NONSTANDARD_PORT2,
      ],
      undefined,
      ["subdomain"],
      DOMAIN1_HTTPS_AUTH.origin,
    ],
    [
      "resolveBy matching searchAndDedupeLogins, prefer domain matches then https: scheme over http:",
      // expected:
      [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN2_HTTPS_TO_HTTPS_U2_P2],
      // logins:
      [
        DOMAIN1_HTTP_TO_HTTP_U1_P1,
        DOMAIN1_HTTPS_TO_HTTPS_U1_P1,
        DOMAIN2_HTTP_TO_HTTP_U2_P2,
        DOMAIN2_HTTPS_TO_HTTPS_U2_P2,
      ],
      // uniqueKeys:
      undefined,
      // resolveBy:
      ["subdomain", "actionOrigin", "scheme", "timePasswordChanged"],
      // preferredOrigin:
      DOMAIN1_HTTPS_TO_HTTPS_U1_P1.origin,
    ],
  ];

  for (let tc of testcases) {
    let description = tc.shift();
    let expected = tc.shift();
    let actual = LoginHelper.dedupeLogins(...tc);
    info(`'${description}' actual:\n ${JSON.stringify(actual, null, 2)}`);
    Assert.strictEqual(actual.length, expected.length, `Check: ${description}`);
    for (let [i, login] of expected.entries()) {
      Assert.strictEqual(actual[i], login, `Check index ${i}`);
    }
  }
});

add_task(async function test_dedupeLogins_preferredOriginMissing() {
  let testcases = [
    [
      "resolveBy scheme + timePasswordChanged, missing preferredOrigin",
      /preferredOrigin/,
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
    ],
    [
      "resolveBy timePasswordChanged + scheme, missing preferredOrigin",
      /preferredOrigin/,
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["timePasswordChanged", "scheme"],
    ],
    [
      "resolveBy scheme + timePasswordChanged, empty preferredOrigin",
      /preferredOrigin/,
      [DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
      undefined,
      ["scheme", "timePasswordChanged"],
      "",
    ],
  ];

  for (let tc of testcases) {
    let description = tc.shift();
    let expectedException = tc.shift();
    Assert.throws(
      () => {
        LoginHelper.dedupeLogins(...tc);
      },
      expectedException,
      `Check: ${description}`
    );
  }
});
