michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: var testnum = 0; michael@0: let dbConnection; // used for deleted table tests michael@0: michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: michael@0: function countDeletedEntries(expected) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: let stmt = dbConnection.createAsyncStatement("SELECT COUNT(*) AS numEntries FROM moz_deleted_formhistory"); michael@0: stmt.executeAsync({ michael@0: handleResult: function(resultSet) { michael@0: do_check_eq(expected, resultSet.getNextRow().getResultByName("numEntries")); michael@0: deferred.resolve(); michael@0: }, michael@0: handleError : function () { michael@0: do_throw("Error occurred counting deleted entries: " + error); michael@0: deferred.reject(); michael@0: }, michael@0: handleCompletion : function () { michael@0: stmt.finalize(); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function checkTimeDeleted(guid, checkFunction) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: let stmt = dbConnection.createAsyncStatement("SELECT timeDeleted FROM moz_deleted_formhistory WHERE guid = :guid"); michael@0: stmt.params.guid = guid; michael@0: stmt.executeAsync({ michael@0: handleResult: function(resultSet) { michael@0: checkFunction(resultSet.getNextRow().getResultByName("timeDeleted")); michael@0: deferred.resolve(); michael@0: }, michael@0: handleError : function () { michael@0: do_throw("Error occurred getting deleted entries: " + error); michael@0: deferred.reject(); michael@0: }, michael@0: handleCompletion : function () { michael@0: stmt.finalize(); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseUpdateEntry(op, name, value) michael@0: { michael@0: var change = { op: op }; michael@0: if (name !== null) michael@0: change.fieldname = name; michael@0: if (value !== null) michael@0: change.value = value; michael@0: return promiseUpdate(change); michael@0: } michael@0: michael@0: function promiseUpdate(change) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: FormHistory.update(change, michael@0: { handleError: function (error) { michael@0: do_throw("Error occurred updating form history: " + error); michael@0: deferred.reject(error); michael@0: }, michael@0: handleCompletion: function (reason) { if (!reason) deferred.resolve(); } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseSearchEntries(terms, params) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: let results = []; michael@0: FormHistory.search(terms, params, michael@0: { handleResult: function(result) results.push(result), michael@0: handleError: function (error) { michael@0: do_throw("Error occurred searching form history: " + error); michael@0: deferred.reject(error); michael@0: }, michael@0: handleCompletion: function (reason) { if (!reason) deferred.resolve(results); } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseCountEntries(name, value, checkFn) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: countEntries(name, value, function (result) { checkFn(result); deferred.resolve(); } ); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: add_task(function () michael@0: { michael@0: let oldSupportsDeletedTable = FormHistory._supportsDeletedTable; michael@0: FormHistory._supportsDeletedTable = true; michael@0: michael@0: try { michael@0: michael@0: // ===== test init ===== michael@0: var testfile = do_get_file("formhistory_apitest.sqlite"); michael@0: var profileDir = dirSvc.get("ProfD", Ci.nsIFile); michael@0: michael@0: // Cleanup from any previous tests or failures. michael@0: var destFile = profileDir.clone(); michael@0: destFile.append("formhistory.sqlite"); michael@0: if (destFile.exists()) michael@0: destFile.remove(false); michael@0: michael@0: testfile.copyTo(profileDir, "formhistory.sqlite"); michael@0: michael@0: function checkExists(num) { do_check_true(num > 0); } michael@0: function checkNotExists(num) { do_check_true(num == 0); } michael@0: michael@0: // ===== 1 ===== michael@0: // Check initial state is as expected michael@0: testnum++; michael@0: yield promiseCountEntries("name-A", null, checkExists); michael@0: yield promiseCountEntries("name-B", null, checkExists); michael@0: yield promiseCountEntries("name-C", null, checkExists); michael@0: yield promiseCountEntries("name-D", null, checkExists); michael@0: yield promiseCountEntries("name-A", "value-A", checkExists); michael@0: yield promiseCountEntries("name-B", "value-B1", checkExists); michael@0: yield promiseCountEntries("name-B", "value-B2", checkExists); michael@0: yield promiseCountEntries("name-C", "value-C", checkExists); michael@0: yield promiseCountEntries("name-D", "value-D", checkExists); michael@0: // time-A/B/C/D checked below. michael@0: michael@0: // Delete anything from the deleted table michael@0: let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone(); michael@0: dbFile.append("formhistory.sqlite"); michael@0: dbConnection = Services.storage.openUnsharedDatabase(dbFile); michael@0: michael@0: let deferred = Promise.defer(); michael@0: michael@0: let stmt = dbConnection.createAsyncStatement("DELETE FROM moz_deleted_formhistory"); michael@0: stmt.executeAsync({ michael@0: handleResult: function(resultSet) { }, michael@0: handleError : function () { michael@0: do_throw("Error occurred counting deleted all entries: " + error); michael@0: }, michael@0: handleCompletion : function () { michael@0: stmt.finalize(); michael@0: deferred.resolve(); michael@0: } michael@0: }); michael@0: yield deferred.promise; michael@0: michael@0: // ===== 2 ===== michael@0: // Test looking for nonexistent / bogus data. michael@0: testnum++; michael@0: yield promiseCountEntries("blah", null, checkNotExists); michael@0: yield promiseCountEntries("", null, checkNotExists); michael@0: yield promiseCountEntries("name-A", "blah", checkNotExists); michael@0: yield promiseCountEntries("name-A", "", checkNotExists); michael@0: yield promiseCountEntries("name-A", null, checkExists); michael@0: yield promiseCountEntries("blah", "value-A", checkNotExists); michael@0: yield promiseCountEntries("", "value-A", checkNotExists); michael@0: yield promiseCountEntries(null, "value-A", checkExists); michael@0: michael@0: // Cannot use promiseCountEntries when name and value are null because it treats null values as not set michael@0: // and here a search should be done explicity for null. michael@0: deferred = Promise.defer(); michael@0: yield FormHistory.count({ fieldname: null, value: null }, michael@0: { handleResult: function(result) checkNotExists(result), michael@0: handleError: function (error) { michael@0: do_throw("Error occurred searching form history: " + error); michael@0: }, michael@0: handleCompletion: function(reason) { if (!reason) deferred.resolve() } michael@0: }); michael@0: yield deferred.promise; michael@0: michael@0: // ===== 3 ===== michael@0: // Test removeEntriesForName with a single matching value michael@0: testnum++; michael@0: yield promiseUpdateEntry("remove", "name-A", null); michael@0: michael@0: yield promiseCountEntries("name-A", "value-A", checkNotExists); michael@0: yield promiseCountEntries("name-B", "value-B1", checkExists); michael@0: yield promiseCountEntries("name-B", "value-B2", checkExists); michael@0: yield promiseCountEntries("name-C", "value-C", checkExists); michael@0: yield promiseCountEntries("name-D", "value-D", checkExists); michael@0: yield countDeletedEntries(1); michael@0: michael@0: // ===== 4 ===== michael@0: // Test removeEntriesForName with multiple matching values michael@0: testnum++; michael@0: yield promiseUpdateEntry("remove", "name-B", null); michael@0: michael@0: yield promiseCountEntries("name-A", "value-A", checkNotExists); michael@0: yield promiseCountEntries("name-B", "value-B1", checkNotExists); michael@0: yield promiseCountEntries("name-B", "value-B2", checkNotExists); michael@0: yield promiseCountEntries("name-C", "value-C", checkExists); michael@0: yield promiseCountEntries("name-D", "value-D", checkExists); michael@0: yield countDeletedEntries(3); michael@0: michael@0: // ===== 5 ===== michael@0: // Test removing by time range (single entry, not surrounding entries) michael@0: testnum++; michael@0: yield promiseCountEntries("time-A", null, checkExists); // firstUsed=1000, lastUsed=1000 michael@0: yield promiseCountEntries("time-B", null, checkExists); // firstUsed=1000, lastUsed=1099 michael@0: yield promiseCountEntries("time-C", null, checkExists); // firstUsed=1099, lastUsed=1099 michael@0: yield promiseCountEntries("time-D", null, checkExists); // firstUsed=2001, lastUsed=2001 michael@0: yield promiseUpdate({ op : "remove", firstUsedStart: 1050, firstUsedEnd: 2000 }); michael@0: michael@0: yield promiseCountEntries("time-A", null, checkExists); michael@0: yield promiseCountEntries("time-B", null, checkExists); michael@0: yield promiseCountEntries("time-C", null, checkNotExists); michael@0: yield promiseCountEntries("time-D", null, checkExists); michael@0: yield countDeletedEntries(4); michael@0: michael@0: // ===== 6 ===== michael@0: // Test removing by time range (multiple entries) michael@0: testnum++; michael@0: yield promiseUpdate({ op : "remove", firstUsedStart: 1000, firstUsedEnd: 2000 }); michael@0: michael@0: yield promiseCountEntries("time-A", null, checkNotExists); michael@0: yield promiseCountEntries("time-B", null, checkNotExists); michael@0: yield promiseCountEntries("time-C", null, checkNotExists); michael@0: yield promiseCountEntries("time-D", null, checkExists); michael@0: yield countDeletedEntries(6); michael@0: michael@0: // ===== 7 ===== michael@0: // test removeAllEntries michael@0: testnum++; michael@0: yield promiseUpdateEntry("remove", null, null); michael@0: michael@0: yield promiseCountEntries("name-C", null, checkNotExists); michael@0: yield promiseCountEntries("name-D", null, checkNotExists); michael@0: yield promiseCountEntries("name-C", "value-C", checkNotExists); michael@0: yield promiseCountEntries("name-D", "value-D", checkNotExists); michael@0: michael@0: yield promiseCountEntries(null, null, checkNotExists); michael@0: yield countDeletedEntries(6); michael@0: michael@0: // ===== 8 ===== michael@0: // Add a single entry back michael@0: testnum++; michael@0: yield promiseUpdateEntry("add", "newname-A", "newvalue-A"); michael@0: yield promiseCountEntries("newname-A", "newvalue-A", checkExists); michael@0: michael@0: // ===== 9 ===== michael@0: // Remove the single entry michael@0: testnum++; michael@0: yield promiseUpdateEntry("remove", "newname-A", "newvalue-A"); michael@0: yield promiseCountEntries("newname-A", "newvalue-A", checkNotExists); michael@0: michael@0: // ===== 10 ===== michael@0: // Add a single entry michael@0: testnum++; michael@0: yield promiseUpdateEntry("add", "field1", "value1"); michael@0: yield promiseCountEntries("field1", "value1", checkExists); michael@0: michael@0: let processFirstResult = function processResults(results) michael@0: { michael@0: // Only handle the first result michael@0: if (results.length > 0) { michael@0: let result = results[0]; michael@0: return [result.timesUsed, result.firstUsed, result.lastUsed, result.guid]; michael@0: } michael@0: } michael@0: michael@0: results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], michael@0: { fieldname: "field1", value: "value1" }); michael@0: let [timesUsed, firstUsed, lastUsed] = processFirstResult(results); michael@0: do_check_eq(1, timesUsed); michael@0: do_check_true(firstUsed > 0); michael@0: do_check_true(lastUsed > 0); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 1)); michael@0: michael@0: // ===== 11 ===== michael@0: // Add another single entry michael@0: testnum++; michael@0: yield promiseUpdateEntry("add", "field1", "value1b"); michael@0: yield promiseCountEntries("field1", "value1", checkExists); michael@0: yield promiseCountEntries("field1", "value1b", checkExists); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); michael@0: michael@0: // ===== 12 ===== michael@0: // Update a single entry michael@0: testnum++; michael@0: michael@0: results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1" }); michael@0: let guid = processFirstResult(results)[3]; michael@0: michael@0: yield promiseUpdate({ op : "update", guid: guid, value: "modifiedValue" }); michael@0: yield promiseCountEntries("field1", "modifiedValue", checkExists); michael@0: yield promiseCountEntries("field1", "value1", checkNotExists); michael@0: yield promiseCountEntries("field1", "value1b", checkExists); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); michael@0: michael@0: // ===== 13 ===== michael@0: // Add a single entry with times michael@0: testnum++; michael@0: yield promiseUpdate({ op : "add", fieldname: "field2", value: "value2", michael@0: timesUsed: 20, firstUsed: 100, lastUsed: 500 }); michael@0: michael@0: results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], michael@0: { fieldname: "field2", value: "value2" }); michael@0: [timesUsed, firstUsed, lastUsed] = processFirstResult(results); michael@0: michael@0: do_check_eq(20, timesUsed); michael@0: do_check_eq(100, firstUsed); michael@0: do_check_eq(500, lastUsed); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); michael@0: michael@0: // ===== 14 ===== michael@0: // Bump an entry, which updates its lastUsed field michael@0: testnum++; michael@0: yield promiseUpdate({ op : "bump", fieldname: "field2", value: "value2", michael@0: timesUsed: 20, firstUsed: 100, lastUsed: 500 }); michael@0: results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], michael@0: { fieldname: "field2", value: "value2" }); michael@0: [timesUsed, firstUsed, lastUsed] = processFirstResult(results); michael@0: do_check_eq(21, timesUsed); michael@0: do_check_eq(100, firstUsed); michael@0: do_check_true(lastUsed > 500); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); michael@0: michael@0: // ===== 15 ===== michael@0: // Bump an entry that does not exist michael@0: testnum++; michael@0: yield promiseUpdate({ op : "bump", fieldname: "field3", value: "value3", michael@0: timesUsed: 10, firstUsed: 50, lastUsed: 400 }); michael@0: results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], michael@0: { fieldname: "field3", value: "value3" }); michael@0: [timesUsed, firstUsed, lastUsed] = processFirstResult(results); michael@0: do_check_eq(10, timesUsed); michael@0: do_check_eq(50, firstUsed); michael@0: do_check_eq(400, lastUsed); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); michael@0: michael@0: // ===== 16 ===== michael@0: // Bump an entry with a guid michael@0: testnum++; michael@0: results = yield promiseSearchEntries(["guid"], { fieldname: "field3", value: "value3" }); michael@0: guid = processFirstResult(results)[3]; michael@0: yield promiseUpdate({ op : "bump", guid: guid, timesUsed: 20, firstUsed: 55, lastUsed: 400 }); michael@0: results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], michael@0: { fieldname: "field3", value: "value3" }); michael@0: [timesUsed, firstUsed, lastUsed] = processFirstResult(results); michael@0: do_check_eq(11, timesUsed); michael@0: do_check_eq(50, firstUsed); michael@0: do_check_true(lastUsed > 400); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); michael@0: michael@0: // ===== 17 ===== michael@0: // Remove an entry michael@0: testnum++; michael@0: yield countDeletedEntries(7); michael@0: michael@0: results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1b" }); michael@0: guid = processFirstResult(results)[3]; michael@0: michael@0: yield promiseUpdate({ op : "remove", guid: guid}); michael@0: yield promiseCountEntries("field1", "modifiedValue", checkExists); michael@0: yield promiseCountEntries("field1", "value1b", checkNotExists); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); michael@0: michael@0: yield countDeletedEntries(8); michael@0: yield checkTimeDeleted(guid, function (timeDeleted) do_check_true(timeDeleted > 10000)); michael@0: michael@0: // ===== 18 ===== michael@0: // Add yet another single entry michael@0: testnum++; michael@0: yield promiseUpdate({ op : "add", fieldname: "field4", value: "value4", michael@0: timesUsed: 5, firstUsed: 230, lastUsed: 600 }); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); michael@0: michael@0: // ===== 19 ===== michael@0: // Remove an entry by time michael@0: testnum++; michael@0: yield promiseUpdate({ op : "remove", firstUsedStart: 60, firstUsedEnd: 250 }); michael@0: yield promiseCountEntries("field1", "modifiedValue", checkExists); michael@0: yield promiseCountEntries("field2", "value2", checkNotExists); michael@0: yield promiseCountEntries("field3", "value3", checkExists); michael@0: yield promiseCountEntries("field4", "value4", checkNotExists); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); michael@0: yield countDeletedEntries(10); michael@0: michael@0: // ===== 20 ===== michael@0: // Bump multiple existing entries at once michael@0: testnum++; michael@0: michael@0: yield promiseUpdate([{ op : "add", fieldname: "field5", value: "value5", michael@0: timesUsed: 5, firstUsed: 230, lastUsed: 600 }, michael@0: { op : "add", fieldname: "field6", value: "value6", michael@0: timesUsed: 12, firstUsed: 430, lastUsed: 700 }]); michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); michael@0: michael@0: yield promiseUpdate([ michael@0: { op : "bump", fieldname: "field5", value: "value5" }, michael@0: { op : "bump", fieldname: "field6", value: "value6" }]); michael@0: results = yield promiseSearchEntries(["fieldname", "timesUsed", "firstUsed", "lastUsed"], { }); michael@0: michael@0: do_check_eq(6, results[2].timesUsed); michael@0: do_check_eq(13, results[3].timesUsed); michael@0: do_check_eq(230, results[2].firstUsed); michael@0: do_check_eq(430, results[3].firstUsed); michael@0: do_check_true(results[2].lastUsed > 600); michael@0: do_check_true(results[3].lastUsed > 700); michael@0: michael@0: yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); michael@0: michael@0: } catch (e) { michael@0: throw "FAILED in test #" + testnum + " -- " + e; michael@0: } michael@0: finally { michael@0: FormHistory._supportsDeletedTable = oldSupportsDeletedTable; michael@0: dbConnection.asyncClose(do_test_finished); michael@0: } michael@0: }); michael@0: michael@0: function run_test() run_next_test();