summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js')
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js411
1 files changed, 411 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js b/toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js
new file mode 100644
index 0000000000..f0305e3c69
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_dedupeLogins.js
@@ -0,0 +1,411 @@
+/**
+ * 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.ok(
+ DOMAIN1_HTTP_TO_HTTP_U1_P1.timeLastUsed >
+ DOMAIN1_HTTPS_TO_HTTP_U1_P1.timeLastUsed,
+ "Sanity check timeLastUsed difference"
+ );
+ Assert.ok(
+ DOMAIN1_HTTP_TO_HTTP_U1_P1.timePasswordChanged <
+ DOMAIN1_HTTPS_TO_HTTP_U1_P1.timePasswordChanged,
+ "Sanity check timePasswordChanged difference"
+ );
+ Assert.ok(
+ 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}`
+ );
+ }
+});