|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 var testnum = 0; |
|
6 let dbConnection; // used for deleted table tests |
|
7 |
|
8 Cu.import("resource://gre/modules/Promise.jsm"); |
|
9 |
|
10 function countDeletedEntries(expected) |
|
11 { |
|
12 let deferred = Promise.defer(); |
|
13 let stmt = dbConnection.createAsyncStatement("SELECT COUNT(*) AS numEntries FROM moz_deleted_formhistory"); |
|
14 stmt.executeAsync({ |
|
15 handleResult: function(resultSet) { |
|
16 do_check_eq(expected, resultSet.getNextRow().getResultByName("numEntries")); |
|
17 deferred.resolve(); |
|
18 }, |
|
19 handleError : function () { |
|
20 do_throw("Error occurred counting deleted entries: " + error); |
|
21 deferred.reject(); |
|
22 }, |
|
23 handleCompletion : function () { |
|
24 stmt.finalize(); |
|
25 } |
|
26 }); |
|
27 return deferred.promise; |
|
28 } |
|
29 |
|
30 function checkTimeDeleted(guid, checkFunction) |
|
31 { |
|
32 let deferred = Promise.defer(); |
|
33 let stmt = dbConnection.createAsyncStatement("SELECT timeDeleted FROM moz_deleted_formhistory WHERE guid = :guid"); |
|
34 stmt.params.guid = guid; |
|
35 stmt.executeAsync({ |
|
36 handleResult: function(resultSet) { |
|
37 checkFunction(resultSet.getNextRow().getResultByName("timeDeleted")); |
|
38 deferred.resolve(); |
|
39 }, |
|
40 handleError : function () { |
|
41 do_throw("Error occurred getting deleted entries: " + error); |
|
42 deferred.reject(); |
|
43 }, |
|
44 handleCompletion : function () { |
|
45 stmt.finalize(); |
|
46 } |
|
47 }); |
|
48 return deferred.promise; |
|
49 } |
|
50 |
|
51 function promiseUpdateEntry(op, name, value) |
|
52 { |
|
53 var change = { op: op }; |
|
54 if (name !== null) |
|
55 change.fieldname = name; |
|
56 if (value !== null) |
|
57 change.value = value; |
|
58 return promiseUpdate(change); |
|
59 } |
|
60 |
|
61 function promiseUpdate(change) |
|
62 { |
|
63 let deferred = Promise.defer(); |
|
64 FormHistory.update(change, |
|
65 { handleError: function (error) { |
|
66 do_throw("Error occurred updating form history: " + error); |
|
67 deferred.reject(error); |
|
68 }, |
|
69 handleCompletion: function (reason) { if (!reason) deferred.resolve(); } |
|
70 }); |
|
71 return deferred.promise; |
|
72 } |
|
73 |
|
74 function promiseSearchEntries(terms, params) |
|
75 { |
|
76 let deferred = Promise.defer(); |
|
77 let results = []; |
|
78 FormHistory.search(terms, params, |
|
79 { handleResult: function(result) results.push(result), |
|
80 handleError: function (error) { |
|
81 do_throw("Error occurred searching form history: " + error); |
|
82 deferred.reject(error); |
|
83 }, |
|
84 handleCompletion: function (reason) { if (!reason) deferred.resolve(results); } |
|
85 }); |
|
86 return deferred.promise; |
|
87 } |
|
88 |
|
89 function promiseCountEntries(name, value, checkFn) |
|
90 { |
|
91 let deferred = Promise.defer(); |
|
92 countEntries(name, value, function (result) { checkFn(result); deferred.resolve(); } ); |
|
93 return deferred.promise; |
|
94 } |
|
95 |
|
96 add_task(function () |
|
97 { |
|
98 let oldSupportsDeletedTable = FormHistory._supportsDeletedTable; |
|
99 FormHistory._supportsDeletedTable = true; |
|
100 |
|
101 try { |
|
102 |
|
103 // ===== test init ===== |
|
104 var testfile = do_get_file("formhistory_apitest.sqlite"); |
|
105 var profileDir = dirSvc.get("ProfD", Ci.nsIFile); |
|
106 |
|
107 // Cleanup from any previous tests or failures. |
|
108 var destFile = profileDir.clone(); |
|
109 destFile.append("formhistory.sqlite"); |
|
110 if (destFile.exists()) |
|
111 destFile.remove(false); |
|
112 |
|
113 testfile.copyTo(profileDir, "formhistory.sqlite"); |
|
114 |
|
115 function checkExists(num) { do_check_true(num > 0); } |
|
116 function checkNotExists(num) { do_check_true(num == 0); } |
|
117 |
|
118 // ===== 1 ===== |
|
119 // Check initial state is as expected |
|
120 testnum++; |
|
121 yield promiseCountEntries("name-A", null, checkExists); |
|
122 yield promiseCountEntries("name-B", null, checkExists); |
|
123 yield promiseCountEntries("name-C", null, checkExists); |
|
124 yield promiseCountEntries("name-D", null, checkExists); |
|
125 yield promiseCountEntries("name-A", "value-A", checkExists); |
|
126 yield promiseCountEntries("name-B", "value-B1", checkExists); |
|
127 yield promiseCountEntries("name-B", "value-B2", checkExists); |
|
128 yield promiseCountEntries("name-C", "value-C", checkExists); |
|
129 yield promiseCountEntries("name-D", "value-D", checkExists); |
|
130 // time-A/B/C/D checked below. |
|
131 |
|
132 // Delete anything from the deleted table |
|
133 let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone(); |
|
134 dbFile.append("formhistory.sqlite"); |
|
135 dbConnection = Services.storage.openUnsharedDatabase(dbFile); |
|
136 |
|
137 let deferred = Promise.defer(); |
|
138 |
|
139 let stmt = dbConnection.createAsyncStatement("DELETE FROM moz_deleted_formhistory"); |
|
140 stmt.executeAsync({ |
|
141 handleResult: function(resultSet) { }, |
|
142 handleError : function () { |
|
143 do_throw("Error occurred counting deleted all entries: " + error); |
|
144 }, |
|
145 handleCompletion : function () { |
|
146 stmt.finalize(); |
|
147 deferred.resolve(); |
|
148 } |
|
149 }); |
|
150 yield deferred.promise; |
|
151 |
|
152 // ===== 2 ===== |
|
153 // Test looking for nonexistent / bogus data. |
|
154 testnum++; |
|
155 yield promiseCountEntries("blah", null, checkNotExists); |
|
156 yield promiseCountEntries("", null, checkNotExists); |
|
157 yield promiseCountEntries("name-A", "blah", checkNotExists); |
|
158 yield promiseCountEntries("name-A", "", checkNotExists); |
|
159 yield promiseCountEntries("name-A", null, checkExists); |
|
160 yield promiseCountEntries("blah", "value-A", checkNotExists); |
|
161 yield promiseCountEntries("", "value-A", checkNotExists); |
|
162 yield promiseCountEntries(null, "value-A", checkExists); |
|
163 |
|
164 // Cannot use promiseCountEntries when name and value are null because it treats null values as not set |
|
165 // and here a search should be done explicity for null. |
|
166 deferred = Promise.defer(); |
|
167 yield FormHistory.count({ fieldname: null, value: null }, |
|
168 { handleResult: function(result) checkNotExists(result), |
|
169 handleError: function (error) { |
|
170 do_throw("Error occurred searching form history: " + error); |
|
171 }, |
|
172 handleCompletion: function(reason) { if (!reason) deferred.resolve() } |
|
173 }); |
|
174 yield deferred.promise; |
|
175 |
|
176 // ===== 3 ===== |
|
177 // Test removeEntriesForName with a single matching value |
|
178 testnum++; |
|
179 yield promiseUpdateEntry("remove", "name-A", null); |
|
180 |
|
181 yield promiseCountEntries("name-A", "value-A", checkNotExists); |
|
182 yield promiseCountEntries("name-B", "value-B1", checkExists); |
|
183 yield promiseCountEntries("name-B", "value-B2", checkExists); |
|
184 yield promiseCountEntries("name-C", "value-C", checkExists); |
|
185 yield promiseCountEntries("name-D", "value-D", checkExists); |
|
186 yield countDeletedEntries(1); |
|
187 |
|
188 // ===== 4 ===== |
|
189 // Test removeEntriesForName with multiple matching values |
|
190 testnum++; |
|
191 yield promiseUpdateEntry("remove", "name-B", null); |
|
192 |
|
193 yield promiseCountEntries("name-A", "value-A", checkNotExists); |
|
194 yield promiseCountEntries("name-B", "value-B1", checkNotExists); |
|
195 yield promiseCountEntries("name-B", "value-B2", checkNotExists); |
|
196 yield promiseCountEntries("name-C", "value-C", checkExists); |
|
197 yield promiseCountEntries("name-D", "value-D", checkExists); |
|
198 yield countDeletedEntries(3); |
|
199 |
|
200 // ===== 5 ===== |
|
201 // Test removing by time range (single entry, not surrounding entries) |
|
202 testnum++; |
|
203 yield promiseCountEntries("time-A", null, checkExists); // firstUsed=1000, lastUsed=1000 |
|
204 yield promiseCountEntries("time-B", null, checkExists); // firstUsed=1000, lastUsed=1099 |
|
205 yield promiseCountEntries("time-C", null, checkExists); // firstUsed=1099, lastUsed=1099 |
|
206 yield promiseCountEntries("time-D", null, checkExists); // firstUsed=2001, lastUsed=2001 |
|
207 yield promiseUpdate({ op : "remove", firstUsedStart: 1050, firstUsedEnd: 2000 }); |
|
208 |
|
209 yield promiseCountEntries("time-A", null, checkExists); |
|
210 yield promiseCountEntries("time-B", null, checkExists); |
|
211 yield promiseCountEntries("time-C", null, checkNotExists); |
|
212 yield promiseCountEntries("time-D", null, checkExists); |
|
213 yield countDeletedEntries(4); |
|
214 |
|
215 // ===== 6 ===== |
|
216 // Test removing by time range (multiple entries) |
|
217 testnum++; |
|
218 yield promiseUpdate({ op : "remove", firstUsedStart: 1000, firstUsedEnd: 2000 }); |
|
219 |
|
220 yield promiseCountEntries("time-A", null, checkNotExists); |
|
221 yield promiseCountEntries("time-B", null, checkNotExists); |
|
222 yield promiseCountEntries("time-C", null, checkNotExists); |
|
223 yield promiseCountEntries("time-D", null, checkExists); |
|
224 yield countDeletedEntries(6); |
|
225 |
|
226 // ===== 7 ===== |
|
227 // test removeAllEntries |
|
228 testnum++; |
|
229 yield promiseUpdateEntry("remove", null, null); |
|
230 |
|
231 yield promiseCountEntries("name-C", null, checkNotExists); |
|
232 yield promiseCountEntries("name-D", null, checkNotExists); |
|
233 yield promiseCountEntries("name-C", "value-C", checkNotExists); |
|
234 yield promiseCountEntries("name-D", "value-D", checkNotExists); |
|
235 |
|
236 yield promiseCountEntries(null, null, checkNotExists); |
|
237 yield countDeletedEntries(6); |
|
238 |
|
239 // ===== 8 ===== |
|
240 // Add a single entry back |
|
241 testnum++; |
|
242 yield promiseUpdateEntry("add", "newname-A", "newvalue-A"); |
|
243 yield promiseCountEntries("newname-A", "newvalue-A", checkExists); |
|
244 |
|
245 // ===== 9 ===== |
|
246 // Remove the single entry |
|
247 testnum++; |
|
248 yield promiseUpdateEntry("remove", "newname-A", "newvalue-A"); |
|
249 yield promiseCountEntries("newname-A", "newvalue-A", checkNotExists); |
|
250 |
|
251 // ===== 10 ===== |
|
252 // Add a single entry |
|
253 testnum++; |
|
254 yield promiseUpdateEntry("add", "field1", "value1"); |
|
255 yield promiseCountEntries("field1", "value1", checkExists); |
|
256 |
|
257 let processFirstResult = function processResults(results) |
|
258 { |
|
259 // Only handle the first result |
|
260 if (results.length > 0) { |
|
261 let result = results[0]; |
|
262 return [result.timesUsed, result.firstUsed, result.lastUsed, result.guid]; |
|
263 } |
|
264 } |
|
265 |
|
266 results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], |
|
267 { fieldname: "field1", value: "value1" }); |
|
268 let [timesUsed, firstUsed, lastUsed] = processFirstResult(results); |
|
269 do_check_eq(1, timesUsed); |
|
270 do_check_true(firstUsed > 0); |
|
271 do_check_true(lastUsed > 0); |
|
272 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 1)); |
|
273 |
|
274 // ===== 11 ===== |
|
275 // Add another single entry |
|
276 testnum++; |
|
277 yield promiseUpdateEntry("add", "field1", "value1b"); |
|
278 yield promiseCountEntries("field1", "value1", checkExists); |
|
279 yield promiseCountEntries("field1", "value1b", checkExists); |
|
280 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); |
|
281 |
|
282 // ===== 12 ===== |
|
283 // Update a single entry |
|
284 testnum++; |
|
285 |
|
286 results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1" }); |
|
287 let guid = processFirstResult(results)[3]; |
|
288 |
|
289 yield promiseUpdate({ op : "update", guid: guid, value: "modifiedValue" }); |
|
290 yield promiseCountEntries("field1", "modifiedValue", checkExists); |
|
291 yield promiseCountEntries("field1", "value1", checkNotExists); |
|
292 yield promiseCountEntries("field1", "value1b", checkExists); |
|
293 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); |
|
294 |
|
295 // ===== 13 ===== |
|
296 // Add a single entry with times |
|
297 testnum++; |
|
298 yield promiseUpdate({ op : "add", fieldname: "field2", value: "value2", |
|
299 timesUsed: 20, firstUsed: 100, lastUsed: 500 }); |
|
300 |
|
301 results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], |
|
302 { fieldname: "field2", value: "value2" }); |
|
303 [timesUsed, firstUsed, lastUsed] = processFirstResult(results); |
|
304 |
|
305 do_check_eq(20, timesUsed); |
|
306 do_check_eq(100, firstUsed); |
|
307 do_check_eq(500, lastUsed); |
|
308 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); |
|
309 |
|
310 // ===== 14 ===== |
|
311 // Bump an entry, which updates its lastUsed field |
|
312 testnum++; |
|
313 yield promiseUpdate({ op : "bump", fieldname: "field2", value: "value2", |
|
314 timesUsed: 20, firstUsed: 100, lastUsed: 500 }); |
|
315 results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], |
|
316 { fieldname: "field2", value: "value2" }); |
|
317 [timesUsed, firstUsed, lastUsed] = processFirstResult(results); |
|
318 do_check_eq(21, timesUsed); |
|
319 do_check_eq(100, firstUsed); |
|
320 do_check_true(lastUsed > 500); |
|
321 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); |
|
322 |
|
323 // ===== 15 ===== |
|
324 // Bump an entry that does not exist |
|
325 testnum++; |
|
326 yield promiseUpdate({ op : "bump", fieldname: "field3", value: "value3", |
|
327 timesUsed: 10, firstUsed: 50, lastUsed: 400 }); |
|
328 results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], |
|
329 { fieldname: "field3", value: "value3" }); |
|
330 [timesUsed, firstUsed, lastUsed] = processFirstResult(results); |
|
331 do_check_eq(10, timesUsed); |
|
332 do_check_eq(50, firstUsed); |
|
333 do_check_eq(400, lastUsed); |
|
334 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); |
|
335 |
|
336 // ===== 16 ===== |
|
337 // Bump an entry with a guid |
|
338 testnum++; |
|
339 results = yield promiseSearchEntries(["guid"], { fieldname: "field3", value: "value3" }); |
|
340 guid = processFirstResult(results)[3]; |
|
341 yield promiseUpdate({ op : "bump", guid: guid, timesUsed: 20, firstUsed: 55, lastUsed: 400 }); |
|
342 results = yield promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"], |
|
343 { fieldname: "field3", value: "value3" }); |
|
344 [timesUsed, firstUsed, lastUsed] = processFirstResult(results); |
|
345 do_check_eq(11, timesUsed); |
|
346 do_check_eq(50, firstUsed); |
|
347 do_check_true(lastUsed > 400); |
|
348 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); |
|
349 |
|
350 // ===== 17 ===== |
|
351 // Remove an entry |
|
352 testnum++; |
|
353 yield countDeletedEntries(7); |
|
354 |
|
355 results = yield promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1b" }); |
|
356 guid = processFirstResult(results)[3]; |
|
357 |
|
358 yield promiseUpdate({ op : "remove", guid: guid}); |
|
359 yield promiseCountEntries("field1", "modifiedValue", checkExists); |
|
360 yield promiseCountEntries("field1", "value1b", checkNotExists); |
|
361 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 3)); |
|
362 |
|
363 yield countDeletedEntries(8); |
|
364 yield checkTimeDeleted(guid, function (timeDeleted) do_check_true(timeDeleted > 10000)); |
|
365 |
|
366 // ===== 18 ===== |
|
367 // Add yet another single entry |
|
368 testnum++; |
|
369 yield promiseUpdate({ op : "add", fieldname: "field4", value: "value4", |
|
370 timesUsed: 5, firstUsed: 230, lastUsed: 600 }); |
|
371 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); |
|
372 |
|
373 // ===== 19 ===== |
|
374 // Remove an entry by time |
|
375 testnum++; |
|
376 yield promiseUpdate({ op : "remove", firstUsedStart: 60, firstUsedEnd: 250 }); |
|
377 yield promiseCountEntries("field1", "modifiedValue", checkExists); |
|
378 yield promiseCountEntries("field2", "value2", checkNotExists); |
|
379 yield promiseCountEntries("field3", "value3", checkExists); |
|
380 yield promiseCountEntries("field4", "value4", checkNotExists); |
|
381 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 2)); |
|
382 yield countDeletedEntries(10); |
|
383 |
|
384 // ===== 20 ===== |
|
385 // Bump multiple existing entries at once |
|
386 testnum++; |
|
387 |
|
388 yield promiseUpdate([{ op : "add", fieldname: "field5", value: "value5", |
|
389 timesUsed: 5, firstUsed: 230, lastUsed: 600 }, |
|
390 { op : "add", fieldname: "field6", value: "value6", |
|
391 timesUsed: 12, firstUsed: 430, lastUsed: 700 }]); |
|
392 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); |
|
393 |
|
394 yield promiseUpdate([ |
|
395 { op : "bump", fieldname: "field5", value: "value5" }, |
|
396 { op : "bump", fieldname: "field6", value: "value6" }]); |
|
397 results = yield promiseSearchEntries(["fieldname", "timesUsed", "firstUsed", "lastUsed"], { }); |
|
398 |
|
399 do_check_eq(6, results[2].timesUsed); |
|
400 do_check_eq(13, results[3].timesUsed); |
|
401 do_check_eq(230, results[2].firstUsed); |
|
402 do_check_eq(430, results[3].firstUsed); |
|
403 do_check_true(results[2].lastUsed > 600); |
|
404 do_check_true(results[3].lastUsed > 700); |
|
405 |
|
406 yield promiseCountEntries(null, null, function(num) do_check_eq(num, 4)); |
|
407 |
|
408 } catch (e) { |
|
409 throw "FAILED in test #" + testnum + " -- " + e; |
|
410 } |
|
411 finally { |
|
412 FormHistory._supportsDeletedTable = oldSupportsDeletedTable; |
|
413 dbConnection.asyncClose(do_test_finished); |
|
414 } |
|
415 }); |
|
416 |
|
417 function run_test() run_next_test(); |