Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 Cu.import("resource://gre/modules/PlacesUtils.jsm");
5 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
6 Cu.import("resource://services-common/async.js");
7 Cu.import("resource://services-sync/engines/history.js");
8 Cu.import("resource://services-sync/service.js");
9 Cu.import("resource://services-sync/util.js");
11 const TIMESTAMP1 = (Date.now() - 103406528) * 1000;
12 const TIMESTAMP2 = (Date.now() - 6592903) * 1000;
13 const TIMESTAMP3 = (Date.now() - 123894) * 1000;
15 function queryPlaces(uri, options) {
16 let query = PlacesUtils.history.getNewQuery();
17 query.uri = uri;
18 let res = PlacesUtils.history.executeQuery(query, options);
19 res.root.containerOpen = true;
21 let results = [];
22 for (let i = 0; i < res.root.childCount; i++)
23 results.push(res.root.getChild(i));
24 res.root.containerOpen = false;
25 return results;
26 }
28 function queryHistoryVisits(uri) {
29 let options = PlacesUtils.history.getNewQueryOptions();
30 options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
31 options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
32 options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING;
33 return queryPlaces(uri, options);
34 }
36 function onNextTitleChanged(callback) {
37 PlacesUtils.history.addObserver({
38 onBeginUpdateBatch: function onBeginUpdateBatch() {},
39 onEndUpdateBatch: function onEndUpdateBatch() {},
40 onPageChanged: function onPageChanged() {},
41 onTitleChanged: function onTitleChanged() {
42 PlacesUtils.history.removeObserver(this);
43 Utils.nextTick(callback);
44 },
45 onVisit: function onVisit() {},
46 onDeleteVisits: function onDeleteVisits() {},
47 onPageExpired: function onPageExpired() {},
48 onDeleteURI: function onDeleteURI() {},
49 onClearHistory: function onClearHistory() {},
50 QueryInterface: XPCOMUtils.generateQI([
51 Ci.nsINavHistoryObserver,
52 Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS,
53 Ci.nsISupportsWeakReference
54 ])
55 }, true);
56 }
58 // Ensure exceptions from inside callbacks leads to test failures while
59 // we still clean up properly.
60 function ensureThrows(func) {
61 return function() {
62 try {
63 func.apply(this, arguments);
64 } catch (ex) {
65 PlacesUtils.history.removeAllPages();
66 do_throw(ex);
67 }
68 };
69 }
71 let store = new HistoryEngine(Service)._store;
72 function applyEnsureNoFailures(records) {
73 do_check_eq(store.applyIncomingBatch(records).length, 0);
74 }
76 let fxuri, fxguid, tburi, tbguid;
78 function run_test() {
79 initTestLogging("Trace");
80 run_next_test();
81 }
83 add_test(function test_store() {
84 _("Verify that we've got an empty store to work with.");
85 do_check_empty(store.getAllIDs());
87 _("Let's create an entry in the database.");
88 fxuri = Utils.makeURI("http://getfirefox.com/");
90 let place = {
91 uri: fxuri,
92 title: "Get Firefox!",
93 visits: [{
94 visitDate: TIMESTAMP1,
95 transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
96 }]
97 };
98 PlacesUtils.asyncHistory.updatePlaces(place, {
99 handleError: function handleError() {
100 do_throw("Unexpected error in adding visit.");
101 },
102 handleResult: function handleResult() {},
103 handleCompletion: onVisitAdded
104 });
106 function onVisitAdded() {
107 _("Verify that the entry exists.");
108 let ids = Object.keys(store.getAllIDs());
109 do_check_eq(ids.length, 1);
110 fxguid = ids[0];
111 do_check_true(store.itemExists(fxguid));
113 _("If we query a non-existent record, it's marked as deleted.");
114 let record = store.createRecord("non-existent");
115 do_check_true(record.deleted);
117 _("Verify createRecord() returns a complete record.");
118 record = store.createRecord(fxguid);
119 do_check_eq(record.histUri, fxuri.spec);
120 do_check_eq(record.title, "Get Firefox!");
121 do_check_eq(record.visits.length, 1);
122 do_check_eq(record.visits[0].date, TIMESTAMP1);
123 do_check_eq(record.visits[0].type, Ci.nsINavHistoryService.TRANSITION_LINK);
125 _("Let's modify the record and have the store update the database.");
126 let secondvisit = {date: TIMESTAMP2,
127 type: Ci.nsINavHistoryService.TRANSITION_TYPED};
128 onNextTitleChanged(ensureThrows(function() {
129 let queryres = queryHistoryVisits(fxuri);
130 do_check_eq(queryres.length, 2);
131 do_check_eq(queryres[0].time, TIMESTAMP1);
132 do_check_eq(queryres[0].title, "Hol Dir Firefox!");
133 do_check_eq(queryres[1].time, TIMESTAMP2);
134 do_check_eq(queryres[1].title, "Hol Dir Firefox!");
135 run_next_test();
136 }));
137 applyEnsureNoFailures([
138 {id: fxguid,
139 histUri: record.histUri,
140 title: "Hol Dir Firefox!",
141 visits: [record.visits[0], secondvisit]}
142 ]);
143 }
144 });
146 add_test(function test_store_create() {
147 _("Create a brand new record through the store.");
148 tbguid = Utils.makeGUID();
149 tburi = Utils.makeURI("http://getthunderbird.com");
150 onNextTitleChanged(ensureThrows(function() {
151 do_check_attribute_count(store.getAllIDs(), 2);
152 let queryres = queryHistoryVisits(tburi);
153 do_check_eq(queryres.length, 1);
154 do_check_eq(queryres[0].time, TIMESTAMP3);
155 do_check_eq(queryres[0].title, "The bird is the word!");
156 run_next_test();
157 }));
158 applyEnsureNoFailures([
159 {id: tbguid,
160 histUri: tburi.spec,
161 title: "The bird is the word!",
162 visits: [{date: TIMESTAMP3,
163 type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
164 ]);
165 });
167 add_test(function test_null_title() {
168 _("Make sure we handle a null title gracefully (it can happen in some cases, e.g. for resource:// URLs)");
169 let resguid = Utils.makeGUID();
170 let resuri = Utils.makeURI("unknown://title");
171 applyEnsureNoFailures([
172 {id: resguid,
173 histUri: resuri.spec,
174 title: null,
175 visits: [{date: TIMESTAMP3,
176 type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
177 ]);
178 do_check_attribute_count(store.getAllIDs(), 3);
179 let queryres = queryHistoryVisits(resuri);
180 do_check_eq(queryres.length, 1);
181 do_check_eq(queryres[0].time, TIMESTAMP3);
182 run_next_test();
183 });
185 add_test(function test_invalid_records() {
186 _("Make sure we handle invalid URLs in places databases gracefully.");
187 let connection = PlacesUtils.history
188 .QueryInterface(Ci.nsPIPlacesDatabase)
189 .DBConnection;
190 let stmt = connection.createAsyncStatement(
191 "INSERT INTO moz_places "
192 + "(url, title, rev_host, visit_count, last_visit_date) "
193 + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
194 );
195 Async.querySpinningly(stmt);
196 stmt.finalize();
197 // Add the corresponding visit to retain database coherence.
198 stmt = connection.createAsyncStatement(
199 "INSERT INTO moz_historyvisits "
200 + "(place_id, visit_date, visit_type, session) "
201 + "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), "
202 + TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)"
203 );
204 Async.querySpinningly(stmt);
205 stmt.finalize();
206 do_check_attribute_count(store.getAllIDs(), 4);
208 _("Make sure we report records with invalid URIs.");
209 let invalid_uri_guid = Utils.makeGUID();
210 let failed = store.applyIncomingBatch([{
211 id: invalid_uri_guid,
212 histUri: ":::::::::::::::",
213 title: "Doesn't have a valid URI",
214 visits: [{date: TIMESTAMP3,
215 type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
216 ]);
217 do_check_eq(failed.length, 1);
218 do_check_eq(failed[0], invalid_uri_guid);
220 _("Make sure we handle records with invalid GUIDs gracefully (ignore).");
221 applyEnsureNoFailures([
222 {id: "invalid",
223 histUri: "http://invalid.guid/",
224 title: "Doesn't have a valid GUID",
225 visits: [{date: TIMESTAMP3,
226 type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
227 ]);
229 _("Make sure we report records with invalid visits, gracefully handle non-integer dates.");
230 let no_date_visit_guid = Utils.makeGUID();
231 let no_type_visit_guid = Utils.makeGUID();
232 let invalid_type_visit_guid = Utils.makeGUID();
233 let non_integer_visit_guid = Utils.makeGUID();
234 failed = store.applyIncomingBatch([
235 {id: no_date_visit_guid,
236 histUri: "http://no.date.visit/",
237 title: "Visit has no date",
238 visits: [{date: TIMESTAMP3}]},
239 {id: no_type_visit_guid,
240 histUri: "http://no.type.visit/",
241 title: "Visit has no type",
242 visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
243 {id: invalid_type_visit_guid,
244 histUri: "http://invalid.type.visit/",
245 title: "Visit has invalid type",
246 visits: [{date: TIMESTAMP3,
247 type: Ci.nsINavHistoryService.TRANSITION_LINK - 1}]},
248 {id: non_integer_visit_guid,
249 histUri: "http://non.integer.visit/",
250 title: "Visit has non-integer date",
251 visits: [{date: 1234.567,
252 type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
253 ]);
254 do_check_eq(failed.length, 3);
255 failed.sort();
256 let expected = [no_date_visit_guid,
257 no_type_visit_guid,
258 invalid_type_visit_guid].sort();
259 for (let i = 0; i < expected.length; i++) {
260 do_check_eq(failed[i], expected[i]);
261 }
263 _("Make sure we handle records with javascript: URLs gracefully.");
264 applyEnsureNoFailures([
265 {id: Utils.makeGUID(),
266 histUri: "javascript:''",
267 title: "javascript:''",
268 visits: [{date: TIMESTAMP3,
269 type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
270 ]);
272 _("Make sure we handle records without any visits gracefully.");
273 applyEnsureNoFailures([
274 {id: Utils.makeGUID(),
275 histUri: "http://getfirebug.com",
276 title: "Get Firebug!",
277 visits: []}
278 ]);
280 run_next_test();
281 });
283 add_test(function test_remove() {
284 _("Remove an existent record and a non-existent from the store.");
285 applyEnsureNoFailures([{id: fxguid, deleted: true},
286 {id: Utils.makeGUID(), deleted: true}]);
287 do_check_false(store.itemExists(fxguid));
288 let queryres = queryHistoryVisits(fxuri);
289 do_check_eq(queryres.length, 0);
291 _("Make sure wipe works.");
292 store.wipe();
293 do_check_empty(store.getAllIDs());
294 queryres = queryHistoryVisits(fxuri);
295 do_check_eq(queryres.length, 0);
296 queryres = queryHistoryVisits(tburi);
297 do_check_eq(queryres.length, 0);
298 run_next_test();
299 });
301 add_test(function cleanup() {
302 _("Clean up.");
303 PlacesUtils.history.removeAllPages();
304 run_next_test();
305 });