diff options
Diffstat (limited to 'toolkit/components/satchel/test/unit/test_history_api.js')
-rw-r--r-- | toolkit/components/satchel/test/unit/test_history_api.js | 457 |
1 files changed, 457 insertions, 0 deletions
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(); +} |