diff options
Diffstat (limited to 'toolkit/components/satchel/test/unit')
23 files changed, 1724 insertions, 0 deletions
diff --git a/toolkit/components/satchel/test/unit/.eslintrc.js b/toolkit/components/satchel/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/satchel/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/satchel/test/unit/asyncformhistory_expire.sqlite b/toolkit/components/satchel/test/unit/asyncformhistory_expire.sqlite Binary files differnew file mode 100644 index 000000000..07b43c209 --- /dev/null +++ b/toolkit/components/satchel/test/unit/asyncformhistory_expire.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_1000.sqlite b/toolkit/components/satchel/test/unit/formhistory_1000.sqlite Binary files differnew file mode 100644 index 000000000..5eeab074f --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_1000.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_CORRUPT.sqlite b/toolkit/components/satchel/test/unit/formhistory_CORRUPT.sqlite new file mode 100644 index 000000000..5f7498bfc --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_CORRUPT.sqlite @@ -0,0 +1 @@ +BACON diff --git a/toolkit/components/satchel/test/unit/formhistory_apitest.sqlite b/toolkit/components/satchel/test/unit/formhistory_apitest.sqlite Binary files differnew file mode 100644 index 000000000..00daf03c2 --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_apitest.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_autocomplete.sqlite b/toolkit/components/satchel/test/unit/formhistory_autocomplete.sqlite Binary files differnew file mode 100644 index 000000000..724cff73f --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_autocomplete.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_v3.sqlite b/toolkit/components/satchel/test/unit/formhistory_v3.sqlite Binary files differnew file mode 100644 index 000000000..e0e8fe246 --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_v3.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_v3v4.sqlite b/toolkit/components/satchel/test/unit/formhistory_v3v4.sqlite Binary files differnew file mode 100644 index 000000000..8eab177e9 --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_v3v4.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_v999a.sqlite b/toolkit/components/satchel/test/unit/formhistory_v999a.sqlite Binary files differnew file mode 100644 index 000000000..14279f05f --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_v999a.sqlite diff --git a/toolkit/components/satchel/test/unit/formhistory_v999b.sqlite b/toolkit/components/satchel/test/unit/formhistory_v999b.sqlite Binary files differnew file mode 100644 index 000000000..21d9c1f1c --- /dev/null +++ b/toolkit/components/satchel/test/unit/formhistory_v999b.sqlite diff --git a/toolkit/components/satchel/test/unit/head_satchel.js b/toolkit/components/satchel/test/unit/head_satchel.js new file mode 100644 index 000000000..282d07ba5 --- /dev/null +++ b/toolkit/components/satchel/test/unit/head_satchel.js @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/FormHistory.jsm"); + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cu = Components.utils; + +const CURRENT_SCHEMA = 4; +const PR_HOURS = 60 * 60 * 1000000; + +do_get_profile(); + +var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + +// Send the profile-after-change notification to the form history component to ensure +// that it has been initialized. +var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"]. + getService(Ci.nsIObserver); +formHistoryStartup.observe(null, "profile-after-change", null); + +function getDBVersion(dbfile) { + var ss = Cc["@mozilla.org/storage/service;1"]. + getService(Ci.mozIStorageService); + var dbConnection = ss.openDatabase(dbfile); + var version = dbConnection.schemaVersion; + dbConnection.close(); + + return version; +} + +const isGUID = /[A-Za-z0-9\+\/]{16}/; + +// Find form history entries. +function searchEntries(terms, params, iter) { + let results = []; + FormHistory.search(terms, params, { handleResult: result => results.push(result), + handleError: function (error) { + do_throw("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { if (!reason) iter.next(results); } + }); +} + +// Count the number of entries with the given name and value, and call then(number) +// when done. If name or value is null, then the value of that field does not matter. +function countEntries(name, value, then) { + var obj = {}; + if (name !== null) + obj.fieldname = name; + if (value !== null) + obj.value = value; + + let count = 0; + FormHistory.count(obj, { handleResult: result => count = result, + handleError: function (error) { + do_throw("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { if (!reason) then(count); } + }); +} + +// Perform a single form history update and call then() when done. +function updateEntry(op, name, value, then) { + var obj = { op: op }; + if (name !== null) + obj.fieldname = name; + if (value !== null) + obj.value = value; + updateFormHistory(obj, then); +} + +// Add a single form history entry with the current time and call then() when done. +function addEntry(name, value, then) { + let now = Date.now() * 1000; + updateFormHistory({ op: "add", fieldname: name, value: value, timesUsed: 1, + firstUsed: now, lastUsed: now }, then); +} + +// Wrapper around FormHistory.update which handles errors. Calls then() when done. +function updateFormHistory(changes, then) { + FormHistory.update(changes, { handleError: function (error) { + do_throw("Error occurred updating form history: " + error); + }, + handleCompletion: function (reason) { if (!reason) then(); }, + }); +} + +/** + * Logs info to the console in the standard way (includes the filename). + * + * @param aMessage + * The message to log to the console. + */ +function do_log_info(aMessage) { + print("TEST-INFO | " + _TEST_FILE + " | " + aMessage); +} diff --git a/toolkit/components/satchel/test/unit/perf_autocomplete.js b/toolkit/components/satchel/test/unit/perf_autocomplete.js new file mode 100644 index 000000000..6e8bb5125 --- /dev/null +++ b/toolkit/components/satchel/test/unit/perf_autocomplete.js @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testnum = 0; +var fh; +var fac; +var prefs; + +function countAllEntries() { + let stmt = fh.DBConnection.createStatement("SELECT COUNT(*) as numEntries FROM moz_formhistory"); + do_check_true(stmt.executeStep()); + let numEntries = stmt.row.numEntries; + stmt.finalize(); + return numEntries; +} + +function do_AC_search(searchTerm, previousResult) { + var duration = 0; + var searchCount = 5; + var tempPrevious = null; + var startTime; + for (var i = 0; i < searchCount; i++) { + if (previousResult !== null) + tempPrevious = fac.autoCompleteSearch("searchbar-history", previousResult, null, null); + startTime = Date.now(); + results = fac.autoCompleteSearch("searchbar-history", searchTerm, null, tempPrevious); + duration += Date.now() - startTime; + } + dump("[autoCompleteSearch][test " + testnum + "] for '" + searchTerm + "' "); + if (previousResult !== null) + dump("with '" + previousResult + "' previous result "); + else + dump("w/o previous result "); + dump("took " + duration + " ms with " + results.matchCount + " matches. "); + dump("Average of " + Math.round(duration / searchCount) + " ms per search\n"); + return results; +} + +function run_test() { + try { + + // ===== test init ===== + var testfile = do_get_file("formhistory_1000.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + var results; + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + + fh = Cc["@mozilla.org/satchel/form-history;1"]. + getService(Ci.nsIFormHistory2); + fac = Cc["@mozilla.org/satchel/form-autocomplete;1"]. + getService(Ci.nsIFormAutoComplete); + prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + + timeGroupingSize = prefs.getIntPref("browser.formfill.timeGroupingSize") * 1000 * 1000; + maxTimeGroupings = prefs.getIntPref("browser.formfill.maxTimeGroupings"); + bucketSize = prefs.getIntPref("browser.formfill.bucketSize"); + + // ===== 1 ===== + // Check initial state is as expected + testnum++; + do_check_true(fh.hasEntries); + do_check_eq(1000, countAllEntries()); + fac.autoCompleteSearch("searchbar-history", "zzzzzzzzzz", null, null); // warm-up search + do_check_true(fh.nameExists("searchbar-history")); + + // ===== 2 ===== + // Search for '' with no previous result + testnum++; + results = do_AC_search("", null); + do_check_true(results.matchCount > 0); + + // ===== 3 ===== + // Search for 'r' with no previous result + testnum++; + results = do_AC_search("r", null); + do_check_true(results.matchCount > 0); + + // ===== 4 ===== + // Search for 'r' with '' previous result + testnum++; + results = do_AC_search("r", ""); + do_check_true(results.matchCount > 0); + + // ===== 5 ===== + // Search for 're' with no previous result + testnum++; + results = do_AC_search("re", null); + do_check_true(results.matchCount > 0); + + // ===== 6 ===== + // Search for 're' with 'r' previous result + testnum++; + results = do_AC_search("re", "r"); + do_check_true(results.matchCount > 0); + + // ===== 7 ===== + // Search for 'rea' without previous result + testnum++; + results = do_AC_search("rea", null); + let countREA = results.matchCount; + + // ===== 8 ===== + // Search for 'rea' with 're' previous result + testnum++; + results = do_AC_search("rea", "re"); + do_check_eq(countREA, results.matchCount); + + // ===== 9 ===== + // Search for 'real' with 'rea' previous result + testnum++; + results = do_AC_search("real", "rea"); + let countREAL = results.matchCount; + do_check_true(results.matchCount <= countREA); + + // ===== 10 ===== + // Search for 'real' with 're' previous result + testnum++; + results = do_AC_search("real", "re"); + do_check_eq(countREAL, results.matchCount); + + // ===== 11 ===== + // Search for 'real' with no previous result + testnum++; + results = do_AC_search("real", null); + do_check_eq(countREAL, results.matchCount); + + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } +} diff --git a/toolkit/components/satchel/test/unit/test_async_expire.js b/toolkit/components/satchel/test/unit/test_async_expire.js new file mode 100644 index 000000000..501cbdfe5 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_async_expire.js @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var dbFile, oldSize; +var currentTestIndex = 0; + +function triggerExpiration() { + // We can't easily fake a "daily idle" event, so for testing purposes form + // history listens for another notification to trigger an immediate + // expiration. + Services.obs.notifyObservers(null, "formhistory-expire-now", null); +} + +var checkExists = function(num) { do_check_true(num > 0); next_test(); } +var checkNotExists = function(num) { do_check_true(!num); next_test(); } + +var TestObserver = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), + + observe : function (subject, topic, data) { + do_check_eq(topic, "satchel-storage-changed"); + + if (data == "formhistory-expireoldentries") { + next_test(); + } + } +}; + +function test_finished() { + // Make sure we always reset prefs. + if (Services.prefs.prefHasUserValue("browser.formfill.expire_days")) + Services.prefs.clearUserPref("browser.formfill.expire_days"); + + do_test_finished(); +} + +var iter = tests(); + +function run_test() +{ + do_test_pending(); + iter.next(); +} + +function next_test() +{ + iter.next(); +} + +function* tests() +{ + Services.obs.addObserver(TestObserver, "satchel-storage-changed", true); + + // ===== test init ===== + var testfile = do_get_file("asyncformhistory_expire.sqlite"); + var profileDir = do_get_profile(); + + // Cleanup from any previous tests or failures. + dbFile = profileDir.clone(); + dbFile.append("formhistory.sqlite"); + if (dbFile.exists()) + dbFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + do_check_true(dbFile.exists()); + + // We're going to clear this at the end, so it better have the default value now. + do_check_false(Services.prefs.prefHasUserValue("browser.formfill.expire_days")); + + // Sanity check initial state + yield countEntries(null, null, function(num) { do_check_eq(508, num); next_test(); }); + yield countEntries("name-A", "value-A", checkExists); // lastUsed == distant past + yield countEntries("name-B", "value-B", checkExists); // lastUsed == distant future + + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + + // Add a new entry + yield countEntries("name-C", "value-C", checkNotExists); + yield addEntry("name-C", "value-C", next_test); + yield countEntries("name-C", "value-C", checkExists); + + // Update some existing entries to have ages relative to when the test runs. + var now = 1000 * Date.now(); + let updateLastUsed = function updateLastUsedFn(results, age) + { + let lastUsed = now - age * 24 * PR_HOURS; + + let changes = [ ]; + for (let r = 0; r < results.length; r++) { + changes.push({ op: "update", lastUsed: lastUsed, guid: results[r].guid }); + } + + return changes; + } + + let results = yield searchEntries(["guid"], { lastUsed: 181 }, iter); + yield updateFormHistory(updateLastUsed(results, 181), next_test); + + results = yield searchEntries(["guid"], { lastUsed: 179 }, iter); + yield updateFormHistory(updateLastUsed(results, 179), next_test); + + results = yield searchEntries(["guid"], { lastUsed: 31 }, iter); + yield updateFormHistory(updateLastUsed(results, 31), next_test); + + results = yield searchEntries(["guid"], { lastUsed: 29 }, iter); + yield updateFormHistory(updateLastUsed(results, 29), next_test); + + results = yield searchEntries(["guid"], { lastUsed: 9999 }, iter); + yield updateFormHistory(updateLastUsed(results, 11), next_test); + + results = yield searchEntries(["guid"], { lastUsed: 9 }, iter); + yield updateFormHistory(updateLastUsed(results, 9), next_test); + + yield countEntries("name-A", "value-A", checkExists); + yield countEntries("181DaysOld", "foo", checkExists); + yield countEntries("179DaysOld", "foo", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(509, num); next_test(); }); + + // 2 entries are expected to expire. + triggerExpiration(); + yield; + + yield countEntries("name-A", "value-A", checkNotExists); + yield countEntries("181DaysOld", "foo", checkNotExists); + yield countEntries("179DaysOld", "foo", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(507, num); next_test(); }); + + // And again. No change expected. + triggerExpiration(); + yield; + + yield countEntries(null, null, function(num) { do_check_eq(507, num); next_test(); }); + + // Set formfill pref to 30 days. + Services.prefs.setIntPref("browser.formfill.expire_days", 30); + yield countEntries("179DaysOld", "foo", checkExists); + yield countEntries("bar", "31days", checkExists); + yield countEntries("bar", "29days", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(507, num); next_test(); }); + + triggerExpiration(); + yield; + + yield countEntries("179DaysOld", "foo", checkNotExists); + yield countEntries("bar", "31days", checkNotExists); + yield countEntries("bar", "29days", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(505, num); next_test(); }); + + // Set override pref to 10 days and expire. This expires a large batch of + // entries, and should trigger a VACCUM to reduce file size. + Services.prefs.setIntPref("browser.formfill.expire_days", 10); + + yield countEntries("bar", "29days", checkExists); + yield countEntries("9DaysOld", "foo", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(505, num); next_test(); }); + + triggerExpiration(); + yield; + + yield countEntries("bar", "29days", checkNotExists); + yield countEntries("9DaysOld", "foo", checkExists); + yield countEntries("name-B", "value-B", checkExists); + yield countEntries("name-C", "value-C", checkExists); + yield countEntries(null, null, function(num) { do_check_eq(3, num); next_test(); }); + + test_finished(); +} diff --git a/toolkit/components/satchel/test/unit/test_autocomplete.js b/toolkit/components/satchel/test/unit/test_autocomplete.js new file mode 100644 index 000000000..211753809 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_autocomplete.js @@ -0,0 +1,266 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var testnum = 0; +var fac; +var prefs; + +var numRecords, timeGroupingSize, now; + +const DEFAULT_EXPIRE_DAYS = 180; + +function padLeft(number, length) { + var str = number + ''; + while (str.length < length) + str = '0' + str; + return str; +} + +function getFormExpiryDays() { + if (prefs.prefHasUserValue("browser.formfill.expire_days")) { + return prefs.getIntPref("browser.formfill.expire_days"); + } + return DEFAULT_EXPIRE_DAYS; +} + +function run_test() { + // ===== test init ===== + var testfile = do_get_file("formhistory_autocomplete.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + + fac = Cc["@mozilla.org/satchel/form-autocomplete;1"]. + getService(Ci.nsIFormAutoComplete); + prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + + timeGroupingSize = prefs.getIntPref("browser.formfill.timeGroupingSize") * 1000 * 1000; + + run_next_test(); +} + +add_test(function test0() { + var maxTimeGroupings = prefs.getIntPref("browser.formfill.maxTimeGroupings"); + var bucketSize = prefs.getIntPref("browser.formfill.bucketSize"); + + // ===== Tests with constant timesUsed and varying lastUsed date ===== + // insert 2 records per bucket to check alphabetical sort within + now = 1000 * Date.now(); + numRecords = Math.ceil(maxTimeGroupings / bucketSize) * 2; + + let changes = [ ]; + for (let i = 0; i < numRecords; i+=2) { + let useDate = now - (i/2 * bucketSize * timeGroupingSize); + + changes.push({ op : "add", fieldname: "field1", value: "value" + padLeft(numRecords - 1 - i, 2), + timesUsed: 1, firstUsed: useDate, lastUsed: useDate }); + changes.push({ op : "add", fieldname: "field1", value: "value" + padLeft(numRecords - 2 - i, 2), + timesUsed: 1, firstUsed: useDate, lastUsed: useDate }); + } + + updateFormHistory(changes, run_next_test); +}); + +add_test(function test1() { + do_log_info("Check initial state is as expected"); + + countEntries(null, null, function () { + countEntries("field1", null, function (count) { + do_check_true(count > 0); + run_next_test(); + }); + }); +}); + +add_test(function test2() { + do_log_info("Check search contains all entries"); + + fac.autoCompleteSearchAsync("field1", "", null, null, null, { + onSearchCompletion : function(aResults) { + do_check_eq(numRecords, aResults.matchCount); + run_next_test(); + } + }); +}); + +add_test(function test3() { + do_log_info("Check search result ordering with empty search term"); + + let lastFound = numRecords; + fac.autoCompleteSearchAsync("field1", "", null, null, null, { + onSearchCompletion : function(aResults) { + for (let i = 0; i < numRecords; i+=2) { + do_check_eq(parseInt(aResults.getValueAt(i + 1).substr(5), 10), --lastFound); + do_check_eq(parseInt(aResults.getValueAt(i).substr(5), 10), --lastFound); + } + run_next_test(); + } + }); +}); + +add_test(function test4() { + do_log_info("Check search result ordering with \"v\""); + + let lastFound = numRecords; + fac.autoCompleteSearchAsync("field1", "v", null, null, null, { + onSearchCompletion : function(aResults) { + for (let i = 0; i < numRecords; i+=2) { + do_check_eq(parseInt(aResults.getValueAt(i + 1).substr(5), 10), --lastFound); + do_check_eq(parseInt(aResults.getValueAt(i).substr(5), 10), --lastFound); + } + run_next_test(); + } + }); +}); + +const timesUsedSamples = 20; + +add_test(function test5() { + do_log_info("Begin tests with constant use dates and varying timesUsed"); + + let changes = []; + for (let i = 0; i < timesUsedSamples; i++) { + let timesUsed = (timesUsedSamples - i); + let change = { op : "add", fieldname: "field2", value: "value" + (timesUsedSamples - 1 - i), + timesUsed: timesUsed * timeGroupingSize, firstUsed: now, lastUsed: now }; + changes.push(change); + } + updateFormHistory(changes, run_next_test); +}); + +add_test(function test6() { + do_log_info("Check search result ordering with empty search term"); + + let lastFound = timesUsedSamples; + fac.autoCompleteSearchAsync("field2", "", null, null, null, { + onSearchCompletion : function(aResults) { + for (let i = 0; i < timesUsedSamples; i++) { + do_check_eq(parseInt(aResults.getValueAt(i).substr(5)), --lastFound); + } + run_next_test(); + } + }); +}); + +add_test(function test7() { + do_log_info("Check search result ordering with \"v\""); + + let lastFound = timesUsedSamples; + fac.autoCompleteSearchAsync("field2", "v", null, null, null, { + onSearchCompletion : function(aResults) { + for (let i = 0; i < timesUsedSamples; i++) { + do_check_eq(parseInt(aResults.getValueAt(i).substr(5)), --lastFound); + } + run_next_test(); + } + }); +}); + +add_test(function test8() { + do_log_info("Check that \"senior citizen\" entries get a bonus (browser.formfill.agedBonus)"); + + let agedDate = 1000 * (Date.now() - getFormExpiryDays() * 24 * 60 * 60 * 1000); + + let changes = [ ]; + changes.push({ op : "add", fieldname: "field3", value: "old but not senior", + timesUsed: 100, firstUsed: (agedDate + 60 * 1000 * 1000), lastUsed: now }); + changes.push({ op : "add", fieldname: "field3", value: "senior citizen", + timesUsed: 100, firstUsed: (agedDate - 60 * 1000 * 1000), lastUsed: now }); + updateFormHistory(changes, run_next_test); +}); + +add_test(function test9() { + fac.autoCompleteSearchAsync("field3", "", null, null, null, { + onSearchCompletion : function(aResults) { + do_check_eq(aResults.getValueAt(0), "senior citizen"); + do_check_eq(aResults.getValueAt(1), "old but not senior"); + run_next_test(); + } + }); +}); + +add_test(function test10() { + do_log_info("Check entries that are really old or in the future"); + + let changes = [ ]; + changes.push({ op : "add", fieldname: "field4", value: "date of 0", + timesUsed: 1, firstUsed: 0, lastUsed: 0 }); + changes.push({ op : "add", fieldname: "field4", value: "in the future 1", + timesUsed: 1, firstUsed: 0, lastUsed: now * 2 }); + changes.push({ op : "add", fieldname: "field4", value: "in the future 2", + timesUsed: 1, firstUsed: now * 2, lastUsed: now * 2 }); + updateFormHistory(changes, run_next_test); +}); + +add_test(function test11() { + fac.autoCompleteSearchAsync("field4", "", null, null, null, { + onSearchCompletion : function(aResults) { + do_check_eq(aResults.matchCount, 3); + run_next_test(); + } + }); +}); + +var syncValues = ["sync1", "sync1a", "sync2", "sync3"] + +add_test(function test12() { + do_log_info("Check old synchronous api"); + + let changes = [ ]; + for (let value of syncValues) { + changes.push({ op : "add", fieldname: "field5", value: value }); + } + updateFormHistory(changes, run_next_test); +}); + +add_test(function test_token_limit_DB() { + function test_token_limit_previousResult(previousResult) { + do_log_info("Check that the number of tokens used in a search is not capped to " + + "MAX_SEARCH_TOKENS when using a previousResult"); + // This provide more accuracy since performance is less of an issue. + // Search for a string where the first 10 tokens match the previous value but the 11th does not + // when re-using a previous result. + fac.autoCompleteSearchAsync("field_token_cap", + "a b c d e f g h i j .", + null, previousResult, null, { + onSearchCompletion : function(aResults) { + do_check_eq(aResults.matchCount, 0, + "All search tokens should be used with " + + "previous results"); + run_next_test(); + } + }); + } + + do_log_info("Check that the number of tokens used in a search is capped to MAX_SEARCH_TOKENS " + + "for performance when querying the DB"); + let changes = [ ]; + changes.push({ op : "add", fieldname: "field_token_cap", + // value with 15 unique tokens + value: "a b c d e f g h i j k l m n o", + timesUsed: 1, firstUsed: 0, lastUsed: 0 }); + updateFormHistory(changes, () => { + // Search for a string where the first 10 tokens match the value above but the 11th does not + // (which would prevent the result from being returned if the 11th term was used). + fac.autoCompleteSearchAsync("field_token_cap", + "a b c d e f g h i j .", + null, null, null, { + onSearchCompletion : function(aResults) { + do_check_eq(aResults.matchCount, 1, + "Only the first MAX_SEARCH_TOKENS tokens " + + "should be used for DB queries"); + test_token_limit_previousResult(aResults); + } + }); + }); +}); diff --git a/toolkit/components/satchel/test/unit/test_db_corrupt.js b/toolkit/components/satchel/test/unit/test_db_corrupt.js new file mode 100644 index 000000000..a6fdc4c02 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_db_corrupt.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var bakFile; + +function run_test() { + // ===== test init ===== + let testfile = do_get_file("formhistory_CORRUPT.sqlite"); + let profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + let destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + bakFile = profileDir.clone(); + bakFile.append("formhistory.sqlite.corrupt"); + if (bakFile.exists()) + bakFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + run_next_test(); +} + +add_test(function test_corruptFormHistoryDB_lazyCorruptInit1() { + do_log_info("ensure FormHistory backs up a corrupt DB on initialization."); + + // DB init is done lazily so the DB shouldn't be created yet. + do_check_false(bakFile.exists()); + // Doing any request to the DB should create it. + countEntries(null, null, run_next_test); +}); + +add_test(function test_corruptFormHistoryDB_lazyCorruptInit2() { + do_check_true(bakFile.exists()); + bakFile.remove(false); + run_next_test(); +}); + + +add_test(function test_corruptFormHistoryDB_emptyInit() { + do_log_info("test that FormHistory initializes an empty DB in place of corrupt DB."); + + FormHistory.count({}, { + handleResult : function(aNumEntries) { + do_check_true(aNumEntries == 0); + FormHistory.count({ fieldname : "name-A", value : "value-A" }, { + handleResult : function(aNumEntries2) { + do_check_true(aNumEntries2 == 0); + run_next_test(); + }, + handleError : function(aError2) { + do_throw("DB initialized after reading a corrupt DB file found an entry."); + } + }); + }, + handleError : function (aError) { + do_throw("DB initialized after reading a corrupt DB file is not empty."); + } + }); +}); + +add_test(function test_corruptFormHistoryDB_addEntry() { + do_log_info("test adding an entry to the empty DB."); + + updateEntry("add", "name-A", "value-A", + function() { + countEntries("name-A", "value-A", + function(count) { + do_check_true(count == 1); + run_next_test(); + }); + }); + }); + +add_test(function test_corruptFormHistoryDB_removeEntry() { + do_log_info("test removing an entry to the empty DB."); + + updateEntry("remove", "name-A", "value-A", + function() { + countEntries("name-A", "value-A", + function(count) { + do_check_true(count == 0); + run_next_test(); + }); + }); + }); diff --git a/toolkit/components/satchel/test/unit/test_db_update_v4.js b/toolkit/components/satchel/test/unit/test_db_update_v4.js new file mode 100644 index 000000000..84b17e8a0 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_db_update_v4.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testnum = 0; + +var iter; + +function run_test() +{ + do_test_pending(); + iter = next_test(); + iter.next(); +} + +function* next_test() +{ + try { + + // ===== test init ===== + var testfile = do_get_file("formhistory_v3.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + do_check_eq(3, getDBVersion(testfile)); + + // ===== 1 ===== + testnum++; + + destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + let dbConnection = Services.storage.openUnsharedDatabase(destFile); + + // check for upgraded schema. + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + + // Check that the index was added + do_check_true(dbConnection.tableExists("moz_deleted_formhistory")); + dbConnection.close(); + + // check for upgraded schema. + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + // check that an entry still exists + yield countEntries("name-A", "value-A", + function (num) { + do_check_true(num > 0); + do_test_finished(); + } + ); + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } +} diff --git a/toolkit/components/satchel/test/unit/test_db_update_v4b.js b/toolkit/components/satchel/test/unit/test_db_update_v4b.js new file mode 100644 index 000000000..356d34a48 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_db_update_v4b.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testnum = 0; + +var iter; + +function run_test() +{ + do_test_pending(); + iter = next_test(); + iter.next(); +} + +function* next_test() +{ + try { + + // ===== test init ===== + var testfile = do_get_file("formhistory_v3v4.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + do_check_eq(3, getDBVersion(testfile)); + + // ===== 1 ===== + testnum++; + + destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + dbConnection = Services.storage.openUnsharedDatabase(destFile); + + // check for upgraded schema. + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + + // Check that the index was added + do_check_true(dbConnection.tableExists("moz_deleted_formhistory")); + dbConnection.close(); + + // check that an entry still exists + yield countEntries("name-A", "value-A", + function (num) { + do_check_true(num > 0); + do_test_finished(); + } + ); + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } +} diff --git a/toolkit/components/satchel/test/unit/test_db_update_v999a.js b/toolkit/components/satchel/test/unit/test_db_update_v999a.js new file mode 100644 index 000000000..0a44d06aa --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_db_update_v999a.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This test uses a formhistory.sqlite with schema version set to 999 (a + * future version). This exercies the code that allows using a future schema + * version as long as the expected columns are present. + * + * Part A tests this when the columns do match, so the DB is used. + * Part B tests this when the columns do *not* match, so the DB is reset. + */ + +var iter = tests(); + +function run_test() +{ + do_test_pending(); + iter.next(); +} + +function next_test() +{ + iter.next(); +} + +function* tests() +{ + try { + var testnum = 0; + + // ===== test init ===== + var testfile = do_get_file("formhistory_v999a.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + do_check_eq(999, getDBVersion(testfile)); + + let checkZero = function(num) { do_check_eq(num, 0); next_test(); } + let checkOne = function(num) { do_check_eq(num, 1); next_test(); } + + // ===== 1 ===== + testnum++; + // Check for expected contents. + yield countEntries(null, null, function(num) { do_check_true(num > 0); next_test(); }); + yield countEntries("name-A", "value-A", checkOne); + yield countEntries("name-B", "value-B", checkOne); + yield countEntries("name-C", "value-C1", checkOne); + yield countEntries("name-C", "value-C2", checkOne); + yield countEntries("name-E", "value-E", checkOne); + + // check for downgraded schema. + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + + // ===== 2 ===== + testnum++; + // Exercise adding and removing a name/value pair + yield countEntries("name-D", "value-D", checkZero); + yield updateEntry("add", "name-D", "value-D", next_test); + yield countEntries("name-D", "value-D", checkOne); + yield updateEntry("remove", "name-D", "value-D", next_test); + yield countEntries("name-D", "value-D", checkZero); + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } + + do_test_finished(); +} diff --git a/toolkit/components/satchel/test/unit/test_db_update_v999b.js b/toolkit/components/satchel/test/unit/test_db_update_v999b.js new file mode 100644 index 000000000..fb0ecd1b7 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_db_update_v999b.js @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This test uses a formhistory.sqlite with schema version set to 999 (a + * future version). This exercies the code that allows using a future schema + * version as long as the expected columns are present. + * + * Part A tests this when the columns do match, so the DB is used. + * Part B tests this when the columns do *not* match, so the DB is reset. + */ + +var iter = tests(); + +function run_test() +{ + do_test_pending(); + iter.next(); +} + +function next_test() +{ + iter.next(); +} + +function* tests() +{ + try { + var testnum = 0; + + // ===== test init ===== + var testfile = do_get_file("formhistory_v999b.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + var bakFile = profileDir.clone(); + bakFile.append("formhistory.sqlite.corrupt"); + if (bakFile.exists()) + bakFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + do_check_eq(999, getDBVersion(testfile)); + + let checkZero = function(num) { do_check_eq(num, 0); next_test(); } + let checkOne = function(num) { do_check_eq(num, 1); next_test(); } + + // ===== 1 ===== + testnum++; + + // Open the DB, ensure that a backup of the corrupt DB is made. + // DB init is done lazily so the DB shouldn't be created yet. + do_check_false(bakFile.exists()); + // Doing any request to the DB should create it. + yield countEntries("", "", next_test); + + do_check_true(bakFile.exists()); + bakFile.remove(false); + + // ===== 2 ===== + testnum++; + // File should be empty + yield countEntries(null, null, function(num) { do_check_false(num); next_test(); }); + yield countEntries("name-A", "value-A", checkZero); + // check for current schema. + do_check_eq(CURRENT_SCHEMA, FormHistory.schemaVersion); + + // ===== 3 ===== + testnum++; + // Try adding an entry + yield updateEntry("add", "name-A", "value-A", next_test); + yield countEntries(null, null, checkOne); + yield countEntries("name-A", "value-A", checkOne); + + // ===== 4 ===== + testnum++; + // Try removing an entry + yield updateEntry("remove", "name-A", "value-A", next_test); + yield countEntries(null, null, checkZero); + yield countEntries("name-A", "value-A", checkZero); + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } + + do_test_finished(); +} diff --git a/toolkit/components/satchel/test/unit/test_history_api.js b/toolkit/components/satchel/test/unit/test_history_api.js new file mode 100644 index 000000000..753504183 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_history_api.js @@ -0,0 +1,457 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testnum = 0; +var dbConnection; // used for deleted table tests + +Cu.import("resource://gre/modules/Promise.jsm"); + +function countDeletedEntries(expected) +{ + let deferred = Promise.defer(); + let stmt = dbConnection.createAsyncStatement("SELECT COUNT(*) AS numEntries FROM moz_deleted_formhistory"); + stmt.executeAsync({ + handleResult: function(resultSet) { + do_check_eq(expected, resultSet.getNextRow().getResultByName("numEntries")); + deferred.resolve(); + }, + handleError : function () { + do_throw("Error occurred counting deleted entries: " + error); + deferred.reject(); + }, + handleCompletion : function () { + stmt.finalize(); + } + }); + return deferred.promise; +} + +function checkTimeDeleted(guid, checkFunction) +{ + let deferred = Promise.defer(); + let stmt = dbConnection.createAsyncStatement("SELECT timeDeleted FROM moz_deleted_formhistory WHERE guid = :guid"); + stmt.params.guid = guid; + stmt.executeAsync({ + handleResult: function(resultSet) { + checkFunction(resultSet.getNextRow().getResultByName("timeDeleted")); + deferred.resolve(); + }, + handleError : function () { + do_throw("Error occurred getting deleted entries: " + error); + deferred.reject(); + }, + handleCompletion : function () { + stmt.finalize(); + } + }); + return deferred.promise; +} + +function promiseUpdateEntry(op, name, value) +{ + var change = { op: op }; + if (name !== null) + change.fieldname = name; + if (value !== null) + change.value = value; + return promiseUpdate(change); +} + +function promiseUpdate(change) { + return new Promise((resolve, reject) => { + FormHistory.update(change, { + handleError(error) { + this._error = error; + }, + handleCompletion(reason) { + if (reason) { + reject(this._error); + } else { + resolve(); + } + } + }); + }); +} + +function promiseSearchEntries(terms, params) +{ + let deferred = Promise.defer(); + let results = []; + FormHistory.search(terms, params, + { handleResult: result => results.push(result), + handleError: function (error) { + do_throw("Error occurred searching form history: " + error); + deferred.reject(error); + }, + handleCompletion: function (reason) { if (!reason) deferred.resolve(results); } + }); + return deferred.promise; +} + +function promiseCountEntries(name, value, checkFn) +{ + let deferred = Promise.defer(); + countEntries(name, value, function (result) { checkFn(result); deferred.resolve(); } ); + return deferred.promise; +} + +add_task(function* () +{ + let oldSupportsDeletedTable = FormHistory._supportsDeletedTable; + FormHistory._supportsDeletedTable = true; + + try { + + // ===== test init ===== + var testfile = do_get_file("formhistory_apitest.sqlite"); + var profileDir = dirSvc.get("ProfD", Ci.nsIFile); + + // Cleanup from any previous tests or failures. + var destFile = profileDir.clone(); + destFile.append("formhistory.sqlite"); + if (destFile.exists()) + destFile.remove(false); + + testfile.copyTo(profileDir, "formhistory.sqlite"); + + function checkExists(num) { do_check_true(num > 0); } + function checkNotExists(num) { do_check_true(num == 0); } + + // ===== 1 ===== + // Check initial state is as expected + testnum++; + yield promiseCountEntries("name-A", null, checkExists); + yield promiseCountEntries("name-B", null, checkExists); + yield promiseCountEntries("name-C", null, checkExists); + yield promiseCountEntries("name-D", null, checkExists); + yield promiseCountEntries("name-A", "value-A", checkExists); + yield promiseCountEntries("name-B", "value-B1", checkExists); + yield promiseCountEntries("name-B", "value-B2", checkExists); + yield promiseCountEntries("name-C", "value-C", checkExists); + yield promiseCountEntries("name-D", "value-D", checkExists); + // time-A/B/C/D checked below. + + // Delete anything from the deleted table + let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone(); + dbFile.append("formhistory.sqlite"); + dbConnection = Services.storage.openUnsharedDatabase(dbFile); + + let deferred = Promise.defer(); + + let stmt = dbConnection.createAsyncStatement("DELETE FROM moz_deleted_formhistory"); + stmt.executeAsync({ + handleResult: function(resultSet) { }, + handleError : function () { + do_throw("Error occurred counting deleted all entries: " + error); + }, + handleCompletion : function () { + stmt.finalize(); + deferred.resolve(); + } + }); + yield deferred.promise; + + // ===== 2 ===== + // Test looking for nonexistent / bogus data. + testnum++; + yield promiseCountEntries("blah", null, checkNotExists); + yield promiseCountEntries("", null, checkNotExists); + yield promiseCountEntries("name-A", "blah", checkNotExists); + yield promiseCountEntries("name-A", "", checkNotExists); + yield promiseCountEntries("name-A", null, checkExists); + yield promiseCountEntries("blah", "value-A", checkNotExists); + yield promiseCountEntries("", "value-A", checkNotExists); + yield promiseCountEntries(null, "value-A", checkExists); + + // Cannot use promiseCountEntries when name and value are null because it treats null values as not set + // and here a search should be done explicity for null. + deferred = Promise.defer(); + yield FormHistory.count({ fieldname: null, value: null }, + { handleResult: result => checkNotExists(result), + handleError: function (error) { + do_throw("Error occurred searching form history: " + error); + }, + handleCompletion: function(reason) { if (!reason) deferred.resolve() } + }); + yield deferred.promise; + + // ===== 3 ===== + // Test removeEntriesForName with a single matching value + testnum++; + yield promiseUpdateEntry("remove", "name-A", null); + + yield promiseCountEntries("name-A", "value-A", checkNotExists); + yield promiseCountEntries("name-B", "value-B1", checkExists); + yield promiseCountEntries("name-B", "value-B2", checkExists); + yield promiseCountEntries("name-C", "value-C", checkExists); + yield promiseCountEntries("name-D", "value-D", checkExists); + yield countDeletedEntries(1); + + // ===== 4 ===== + // Test removeEntriesForName with multiple matching values + testnum++; + yield promiseUpdateEntry("remove", "name-B", null); + + yield promiseCountEntries("name-A", "value-A", checkNotExists); + yield promiseCountEntries("name-B", "value-B1", checkNotExists); + yield promiseCountEntries("name-B", "value-B2", checkNotExists); + yield promiseCountEntries("name-C", "value-C", checkExists); + yield promiseCountEntries("name-D", "value-D", checkExists); + yield countDeletedEntries(3); + + // ===== 5 ===== + // Test removing by time range (single entry, not surrounding entries) + testnum++; + yield promiseCountEntries("time-A", null, checkExists); // firstUsed=1000, lastUsed=1000 + yield promiseCountEntries("time-B", null, checkExists); // firstUsed=1000, lastUsed=1099 + yield promiseCountEntries("time-C", null, checkExists); // firstUsed=1099, lastUsed=1099 + yield promiseCountEntries("time-D", null, checkExists); // firstUsed=2001, lastUsed=2001 + yield promiseUpdate({ op : "remove", firstUsedStart: 1050, firstUsedEnd: 2000 }); + + yield promiseCountEntries("time-A", null, checkExists); + yield promiseCountEntries("time-B", null, checkExists); + yield promiseCountEntries("time-C", null, checkNotExists); + yield promiseCountEntries("time-D", null, checkExists); + yield countDeletedEntries(4); + + // ===== 6 ===== + // Test removing by time range (multiple entries) + testnum++; + yield promiseUpdate({ op : "remove", firstUsedStart: 1000, firstUsedEnd: 2000 }); + + yield promiseCountEntries("time-A", null, checkNotExists); + yield promiseCountEntries("time-B", null, checkNotExists); + yield promiseCountEntries("time-C", null, checkNotExists); + yield promiseCountEntries("time-D", null, checkExists); + yield countDeletedEntries(6); + + // ===== 7 ===== + // test removeAllEntries + testnum++; + yield promiseUpdateEntry("remove", null, null); + + yield promiseCountEntries("name-C", null, checkNotExists); + yield promiseCountEntries("name-D", null, checkNotExists); + yield promiseCountEntries("name-C", "value-C", checkNotExists); + yield promiseCountEntries("name-D", "value-D", checkNotExists); + + yield promiseCountEntries(null, null, checkNotExists); + yield countDeletedEntries(6); + + // ===== 8 ===== + // Add a single entry back + testnum++; + yield promiseUpdateEntry("add", "newname-A", "newvalue-A"); + yield promiseCountEntries("newname-A", "newvalue-A", checkExists); + + // ===== 9 ===== + // Remove the single entry + testnum++; + yield promiseUpdateEntry("remove", "newname-A", "newvalue-A"); + yield promiseCountEntries("newname-A", "newvalue-A", checkNotExists); + + // ===== 10 ===== + // Add a single entry + testnum++; + yield promiseUpdateEntry("add", "field1", "value1"); + yield promiseCountEntries("field1", "value1", checkExists); + + let processFirstResult = function processResults(results) + { + // Only handle the first result + if (results.length > 0) { + let result = results[0]; + return [result.timesUsed, result.firstUsed, result.lastUsed, result.guid]; + } + return undefined; + } + + results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], + { fieldname: "field1", value: "value1" }); + let [timesUsed, firstUsed, lastUsed] = processFirstResult(results); + do_check_eq(1, timesUsed); + do_check_true(firstUsed > 0); + do_check_true(lastUsed > 0); + yield promiseCountEntries(null, null, num => do_check_eq(num, 1)); + + // ===== 11 ===== + // Add another single entry + testnum++; + yield promiseUpdateEntry("add", "field1", "value1b"); + yield promiseCountEntries("field1", "value1", checkExists); + yield promiseCountEntries("field1", "value1b", checkExists); + yield promiseCountEntries(null, null, num => do_check_eq(num, 2)); + + // ===== 12 ===== + // Update a single entry + testnum++; + + results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1" }); + let guid = processFirstResult(results)[3]; + + yield promiseUpdate({ op : "update", guid: guid, value: "modifiedValue" }); + yield promiseCountEntries("field1", "modifiedValue", checkExists); + yield promiseCountEntries("field1", "value1", checkNotExists); + yield promiseCountEntries("field1", "value1b", checkExists); + yield promiseCountEntries(null, null, num => do_check_eq(num, 2)); + + // ===== 13 ===== + // Add a single entry with times + testnum++; + yield promiseUpdate({ op : "add", fieldname: "field2", value: "value2", + timesUsed: 20, firstUsed: 100, lastUsed: 500 }); + + results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], + { fieldname: "field2", value: "value2" }); + [timesUsed, firstUsed, lastUsed] = processFirstResult(results); + + do_check_eq(20, timesUsed); + do_check_eq(100, firstUsed); + do_check_eq(500, lastUsed); + yield promiseCountEntries(null, null, num => do_check_eq(num, 3)); + + // ===== 14 ===== + // Bump an entry, which updates its lastUsed field + testnum++; + yield promiseUpdate({ op : "bump", fieldname: "field2", value: "value2", + timesUsed: 20, firstUsed: 100, lastUsed: 500 }); + results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], + { fieldname: "field2", value: "value2" }); + [timesUsed, firstUsed, lastUsed] = processFirstResult(results); + do_check_eq(21, timesUsed); + do_check_eq(100, firstUsed); + do_check_true(lastUsed > 500); + yield promiseCountEntries(null, null, num => do_check_eq(num, 3)); + + // ===== 15 ===== + // Bump an entry that does not exist + testnum++; + yield promiseUpdate({ op : "bump", fieldname: "field3", value: "value3", + timesUsed: 10, firstUsed: 50, lastUsed: 400 }); + results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], + { fieldname: "field3", value: "value3" }); + [timesUsed, firstUsed, lastUsed] = processFirstResult(results); + do_check_eq(10, timesUsed); + do_check_eq(50, firstUsed); + do_check_eq(400, lastUsed); + yield promiseCountEntries(null, null, num => do_check_eq(num, 4)); + + // ===== 16 ===== + // Bump an entry with a guid + testnum++; + results = yield promiseSearchEntries(["guid"], { fieldname: "field3", value: "value3" }); + guid = processFirstResult(results)[3]; + yield promiseUpdate({ op : "bump", guid: guid, timesUsed: 20, firstUsed: 55, lastUsed: 400 }); + results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], + { fieldname: "field3", value: "value3" }); + [timesUsed, firstUsed, lastUsed] = processFirstResult(results); + do_check_eq(11, timesUsed); + do_check_eq(50, firstUsed); + do_check_true(lastUsed > 400); + yield promiseCountEntries(null, null, num => do_check_eq(num, 4)); + + // ===== 17 ===== + // Remove an entry + testnum++; + yield countDeletedEntries(7); + + results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1b" }); + guid = processFirstResult(results)[3]; + + yield promiseUpdate({ op : "remove", guid: guid}); + yield promiseCountEntries("field1", "modifiedValue", checkExists); + yield promiseCountEntries("field1", "value1b", checkNotExists); + yield promiseCountEntries(null, null, num => do_check_eq(num, 3)); + + yield countDeletedEntries(8); + yield checkTimeDeleted(guid, timeDeleted => do_check_true(timeDeleted > 10000)); + + // ===== 18 ===== + // Add yet another single entry + testnum++; + yield promiseUpdate({ op : "add", fieldname: "field4", value: "value4", + timesUsed: 5, firstUsed: 230, lastUsed: 600 }); + yield promiseCountEntries(null, null, num => do_check_eq(num, 4)); + + // ===== 19 ===== + // Remove an entry by time + testnum++; + yield promiseUpdate({ op : "remove", firstUsedStart: 60, firstUsedEnd: 250 }); + yield promiseCountEntries("field1", "modifiedValue", checkExists); + yield promiseCountEntries("field2", "value2", checkNotExists); + yield promiseCountEntries("field3", "value3", checkExists); + yield promiseCountEntries("field4", "value4", checkNotExists); + yield promiseCountEntries(null, null, num => do_check_eq(num, 2)); + yield countDeletedEntries(10); + + // ===== 20 ===== + // Bump multiple existing entries at once + testnum++; + + yield promiseUpdate([{ op : "add", fieldname: "field5", value: "value5", + timesUsed: 5, firstUsed: 230, lastUsed: 600 }, + { op : "add", fieldname: "field6", value: "value6", + timesUsed: 12, firstUsed: 430, lastUsed: 700 }]); + yield promiseCountEntries(null, null, num => do_check_eq(num, 4)); + + yield promiseUpdate([ + { op : "bump", fieldname: "field5", value: "value5" }, + { op : "bump", fieldname: "field6", value: "value6" }]); + results = yield promiseSearchEntries(["fieldname", "timesUsed", "firstUsed", "lastUsed"], { }); + + do_check_eq(6, results[2].timesUsed); + do_check_eq(13, results[3].timesUsed); + do_check_eq(230, results[2].firstUsed); + do_check_eq(430, results[3].firstUsed); + do_check_true(results[2].lastUsed > 600); + do_check_true(results[3].lastUsed > 700); + + yield promiseCountEntries(null, null, num => do_check_eq(num, 4)); + + // ===== 21 ===== + // Check update fails if form history is disabled and the operation is not a + // pure removal. + testnum++; + Services.prefs.setBoolPref("browser.formfill.enable", false); + + // Cannot use arrow functions, see bug 1237961. + Assert.rejects(promiseUpdate( + { op : "bump", fieldname: "field5", value: "value5" }), + function(err) { return err.result == Ci.mozIStorageError.MISUSE; }, + "bumping when form history is disabled should fail"); + Assert.rejects(promiseUpdate( + { op : "add", fieldname: "field5", value: "value5" }), + function(err) { return err.result == Ci.mozIStorageError.MISUSE; }, + "Adding when form history is disabled should fail"); + Assert.rejects(promiseUpdate([ + { op : "update", fieldname: "field5", value: "value5" }, + { op : "remove", fieldname: "field5", value: "value5" } + ]), + function(err) { return err.result == Ci.mozIStorageError.MISUSE; }, + "mixed operations when form history is disabled should fail"); + Assert.rejects(promiseUpdate([ + null, undefined, "", 1, {}, + { op : "remove", fieldname: "field5", value: "value5" } + ]), + function(err) { return err.result == Ci.mozIStorageError.MISUSE; }, + "Invalid entries when form history is disabled should fail"); + + // Remove should work though. + yield promiseUpdate([{ op: "remove", fieldname: "field5", value: null }, + { op: "remove", fieldname: null, value: null }]); + Services.prefs.clearUserPref("browser.formfill.enable"); + + } catch (e) { + throw "FAILED in test #" + testnum + " -- " + e; + } + finally { + FormHistory._supportsDeletedTable = oldSupportsDeletedTable; + dbConnection.asyncClose(do_test_finished); + } +}); + +function run_test() { + return run_next_test(); +} diff --git a/toolkit/components/satchel/test/unit/test_notify.js b/toolkit/components/satchel/test/unit/test_notify.js new file mode 100644 index 000000000..556ecd4b0 --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_notify.js @@ -0,0 +1,158 @@ +/* + * Test suite for satchel notifications + * + * Tests notifications dispatched when modifying form history. + * + */ + +var expectedNotification; +var expectedData; + +var TestObserver = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), + + observe : function (subject, topic, data) { + do_check_eq(topic, "satchel-storage-changed"); + do_check_eq(data, expectedNotification); + + switch (data) { + case "formhistory-add": + case "formhistory-update": + do_check_true(subject instanceof Ci.nsISupportsString); + do_check_true(isGUID.test(subject.toString())); + break; + case "formhistory-remove": + do_check_eq(null, subject); + break; + default: + do_throw("Unhandled notification: " + data + " / " + topic); + } + + expectedNotification = null; + expectedData = null; + } +}; + +var testIterator = null; + +function run_test() { + do_test_pending(); + testIterator = run_test_steps(); + testIterator.next(); +} + +function next_test() +{ + testIterator.next(); +} + +function* run_test_steps() { + +try { + +var testnum = 0; +var testdesc = "Setup of test form history entries"; + +var entry1 = ["entry1", "value1"]; + +/* ========== 1 ========== */ +testnum = 1; +testdesc = "Initial connection to storage module" + +yield updateEntry("remove", null, null, next_test); +yield countEntries(null, null, function (num) { do_check_false(num, "Checking initial DB is empty"); next_test(); }); + +// Add the observer +var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); +os.addObserver(TestObserver, "satchel-storage-changed", false); + +/* ========== 2 ========== */ +testnum++; +testdesc = "addEntry"; + +expectedNotification = "formhistory-add"; +expectedData = entry1; + +yield updateEntry("add", entry1[0], entry1[1], next_test); +do_check_eq(expectedNotification, null); // check that observer got a notification + +yield countEntries(entry1[0], entry1[1], function (num) { do_check_true(num > 0); next_test(); }); + +/* ========== 3 ========== */ +testnum++; +testdesc = "modifyEntry"; + +expectedNotification = "formhistory-update"; +expectedData = entry1; +// will update previous entry +yield updateEntry("update", entry1[0], entry1[1], next_test); +yield countEntries(entry1[0], entry1[1], function (num) { do_check_true(num > 0); next_test(); }); + +do_check_eq(expectedNotification, null); + +/* ========== 4 ========== */ +testnum++; +testdesc = "removeEntry"; + +expectedNotification = "formhistory-remove"; +expectedData = entry1; +yield updateEntry("remove", entry1[0], entry1[1], next_test); + +do_check_eq(expectedNotification, null); +yield countEntries(entry1[0], entry1[1], function(num) { do_check_false(num, "doesn't exist after remove"); next_test(); }); + +/* ========== 5 ========== */ +testnum++; +testdesc = "removeAllEntries"; + +expectedNotification = "formhistory-remove"; +expectedData = null; // no data expected +yield updateEntry("remove", null, null, next_test); + +do_check_eq(expectedNotification, null); + +/* ========== 6 ========== */ +testnum++; +testdesc = "removeAllEntries (again)"; + +expectedNotification = "formhistory-remove"; +expectedData = null; +yield updateEntry("remove", null, null, next_test); + +do_check_eq(expectedNotification, null); + +/* ========== 7 ========== */ +testnum++; +testdesc = "removeEntriesForName"; + +expectedNotification = "formhistory-remove"; +expectedData = "field2"; +yield updateEntry("remove", null, "field2", next_test); + +do_check_eq(expectedNotification, null); + +/* ========== 8 ========== */ +testnum++; +testdesc = "removeEntriesByTimeframe"; + +expectedNotification = "formhistory-remove"; +expectedData = [10, 99999999999]; + +yield FormHistory.update({ op: "remove", firstUsedStart: expectedData[0], firstUsedEnd: expectedData[1] }, + { handleCompletion: function(reason) { if (!reason) next_test() }, + handleErrors: function (error) { + do_throw("Error occurred updating form history: " + error); + } + }); + +do_check_eq(expectedNotification, null); + +os.removeObserver(TestObserver, "satchel-storage-changed", false); + +do_test_finished(); + +} catch (e) { + throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e; +} +} diff --git a/toolkit/components/satchel/test/unit/test_previous_result.js b/toolkit/components/satchel/test/unit/test_previous_result.js new file mode 100644 index 000000000..06e5a385b --- /dev/null +++ b/toolkit/components/satchel/test/unit/test_previous_result.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var aaaListener = { + onSearchResult: function(search, result) { + do_check_eq(result.searchString, "aaa"); + do_test_finished(); + } +}; + +var aaListener = { + onSearchResult: function(search, result) { + do_check_eq(result.searchString, "aa"); + search.startSearch("aaa", "", result, aaaListener); + } +}; + +function run_test() +{ + do_test_pending(); + let search = Cc['@mozilla.org/autocomplete/search;1?name=form-history']. + getService(Components.interfaces.nsIAutoCompleteSearch); + search.startSearch("aa", "", null, aaListener); +} diff --git a/toolkit/components/satchel/test/unit/xpcshell.ini b/toolkit/components/satchel/test/unit/xpcshell.ini new file mode 100644 index 000000000..4a41b47d6 --- /dev/null +++ b/toolkit/components/satchel/test/unit/xpcshell.ini @@ -0,0 +1,26 @@ +[DEFAULT] +head = head_satchel.js +tail = +skip-if = toolkit == 'android' +support-files = + asyncformhistory_expire.sqlite + formhistory_1000.sqlite + formhistory_CORRUPT.sqlite + formhistory_apitest.sqlite + formhistory_autocomplete.sqlite + formhistory_v3.sqlite + formhistory_v3v4.sqlite + formhistory_v999a.sqlite + formhistory_v999b.sqlite + perf_autocomplete.js + +[test_async_expire.js] +[test_autocomplete.js] +[test_db_corrupt.js] +[test_db_update_v4.js] +[test_db_update_v4b.js] +[test_db_update_v999a.js] +[test_db_update_v999b.js] +[test_history_api.js] +[test_notify.js] +[test_previous_result.js] |