services/sync/tests/unit/test_history_store.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/sync/tests/unit/test_history_store.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,305 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +Cu.import("resource://gre/modules/PlacesUtils.jsm");
     1.8 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
     1.9 +Cu.import("resource://services-common/async.js");
    1.10 +Cu.import("resource://services-sync/engines/history.js");
    1.11 +Cu.import("resource://services-sync/service.js");
    1.12 +Cu.import("resource://services-sync/util.js");
    1.13 +
    1.14 +const TIMESTAMP1 = (Date.now() - 103406528) * 1000;
    1.15 +const TIMESTAMP2 = (Date.now() - 6592903) * 1000;
    1.16 +const TIMESTAMP3 = (Date.now() - 123894) * 1000;
    1.17 +
    1.18 +function queryPlaces(uri, options) {
    1.19 +  let query = PlacesUtils.history.getNewQuery();
    1.20 +  query.uri = uri;
    1.21 +  let res = PlacesUtils.history.executeQuery(query, options);
    1.22 +  res.root.containerOpen = true;
    1.23 +
    1.24 +  let results = [];
    1.25 +  for (let i = 0; i < res.root.childCount; i++)
    1.26 +    results.push(res.root.getChild(i));
    1.27 +  res.root.containerOpen = false;
    1.28 +  return results;
    1.29 +}
    1.30 +
    1.31 +function queryHistoryVisits(uri) {
    1.32 +  let options = PlacesUtils.history.getNewQueryOptions();
    1.33 +  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
    1.34 +  options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
    1.35 +  options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING;
    1.36 +  return queryPlaces(uri, options);
    1.37 +}
    1.38 +
    1.39 +function onNextTitleChanged(callback) {
    1.40 +  PlacesUtils.history.addObserver({
    1.41 +    onBeginUpdateBatch: function onBeginUpdateBatch() {},
    1.42 +    onEndUpdateBatch: function onEndUpdateBatch() {},
    1.43 +    onPageChanged: function onPageChanged() {},
    1.44 +    onTitleChanged: function onTitleChanged() {
    1.45 +      PlacesUtils.history.removeObserver(this);
    1.46 +      Utils.nextTick(callback);
    1.47 +    },
    1.48 +    onVisit: function onVisit() {},
    1.49 +    onDeleteVisits: function onDeleteVisits() {},
    1.50 +    onPageExpired: function onPageExpired() {},
    1.51 +    onDeleteURI: function onDeleteURI() {},
    1.52 +    onClearHistory: function onClearHistory() {},
    1.53 +    QueryInterface: XPCOMUtils.generateQI([
    1.54 +      Ci.nsINavHistoryObserver,
    1.55 +      Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS,
    1.56 +      Ci.nsISupportsWeakReference
    1.57 +    ])
    1.58 +  }, true);
    1.59 +}
    1.60 +
    1.61 +// Ensure exceptions from inside callbacks leads to test failures while
    1.62 +// we still clean up properly.
    1.63 +function ensureThrows(func) {
    1.64 +  return function() {
    1.65 +    try {
    1.66 +      func.apply(this, arguments);
    1.67 +    } catch (ex) {
    1.68 +      PlacesUtils.history.removeAllPages();
    1.69 +      do_throw(ex);
    1.70 +    }
    1.71 +  };
    1.72 +}
    1.73 +
    1.74 +let store = new HistoryEngine(Service)._store;
    1.75 +function applyEnsureNoFailures(records) {
    1.76 +  do_check_eq(store.applyIncomingBatch(records).length, 0);
    1.77 +}
    1.78 +
    1.79 +let fxuri, fxguid, tburi, tbguid;
    1.80 +
    1.81 +function run_test() {
    1.82 +  initTestLogging("Trace");
    1.83 +  run_next_test();
    1.84 +}
    1.85 +
    1.86 +add_test(function test_store() {
    1.87 +  _("Verify that we've got an empty store to work with.");
    1.88 +  do_check_empty(store.getAllIDs());
    1.89 +
    1.90 +  _("Let's create an entry in the database.");
    1.91 +  fxuri = Utils.makeURI("http://getfirefox.com/");
    1.92 +
    1.93 +  let place = {
    1.94 +    uri: fxuri,
    1.95 +    title: "Get Firefox!",
    1.96 +    visits: [{
    1.97 +      visitDate: TIMESTAMP1,
    1.98 +      transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
    1.99 +    }]
   1.100 +  };
   1.101 +  PlacesUtils.asyncHistory.updatePlaces(place, {
   1.102 +    handleError: function handleError() {
   1.103 +      do_throw("Unexpected error in adding visit.");
   1.104 +    },
   1.105 +    handleResult: function handleResult() {},
   1.106 +    handleCompletion: onVisitAdded
   1.107 +  });
   1.108 +
   1.109 +  function onVisitAdded() {
   1.110 +    _("Verify that the entry exists.");
   1.111 +    let ids = Object.keys(store.getAllIDs());
   1.112 +    do_check_eq(ids.length, 1);
   1.113 +    fxguid = ids[0];
   1.114 +    do_check_true(store.itemExists(fxguid));
   1.115 +
   1.116 +    _("If we query a non-existent record, it's marked as deleted.");
   1.117 +    let record = store.createRecord("non-existent");
   1.118 +    do_check_true(record.deleted);
   1.119 +
   1.120 +    _("Verify createRecord() returns a complete record.");
   1.121 +    record = store.createRecord(fxguid);
   1.122 +    do_check_eq(record.histUri, fxuri.spec);
   1.123 +    do_check_eq(record.title, "Get Firefox!");
   1.124 +    do_check_eq(record.visits.length, 1);
   1.125 +    do_check_eq(record.visits[0].date, TIMESTAMP1);
   1.126 +    do_check_eq(record.visits[0].type, Ci.nsINavHistoryService.TRANSITION_LINK);
   1.127 +
   1.128 +    _("Let's modify the record and have the store update the database.");
   1.129 +    let secondvisit = {date: TIMESTAMP2,
   1.130 +                       type: Ci.nsINavHistoryService.TRANSITION_TYPED};
   1.131 +    onNextTitleChanged(ensureThrows(function() {
   1.132 +      let queryres = queryHistoryVisits(fxuri);
   1.133 +      do_check_eq(queryres.length, 2);
   1.134 +      do_check_eq(queryres[0].time, TIMESTAMP1);
   1.135 +      do_check_eq(queryres[0].title, "Hol Dir Firefox!");
   1.136 +      do_check_eq(queryres[1].time, TIMESTAMP2);
   1.137 +      do_check_eq(queryres[1].title, "Hol Dir Firefox!");
   1.138 +      run_next_test();
   1.139 +    }));
   1.140 +    applyEnsureNoFailures([
   1.141 +      {id: fxguid,
   1.142 +       histUri: record.histUri,
   1.143 +       title: "Hol Dir Firefox!",
   1.144 +       visits: [record.visits[0], secondvisit]}
   1.145 +    ]);
   1.146 +  }
   1.147 +});
   1.148 +
   1.149 +add_test(function test_store_create() {
   1.150 +  _("Create a brand new record through the store.");
   1.151 +  tbguid = Utils.makeGUID();
   1.152 +  tburi = Utils.makeURI("http://getthunderbird.com");
   1.153 +  onNextTitleChanged(ensureThrows(function() {
   1.154 +    do_check_attribute_count(store.getAllIDs(), 2);
   1.155 +    let queryres = queryHistoryVisits(tburi);
   1.156 +    do_check_eq(queryres.length, 1);
   1.157 +    do_check_eq(queryres[0].time, TIMESTAMP3);
   1.158 +    do_check_eq(queryres[0].title, "The bird is the word!");
   1.159 +    run_next_test();
   1.160 +  }));
   1.161 +  applyEnsureNoFailures([
   1.162 +    {id: tbguid,
   1.163 +     histUri: tburi.spec,
   1.164 +     title: "The bird is the word!",
   1.165 +     visits: [{date: TIMESTAMP3,
   1.166 +               type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
   1.167 +  ]);
   1.168 +});
   1.169 +
   1.170 +add_test(function test_null_title() {
   1.171 +  _("Make sure we handle a null title gracefully (it can happen in some cases, e.g. for resource:// URLs)");
   1.172 +  let resguid = Utils.makeGUID();
   1.173 +  let resuri = Utils.makeURI("unknown://title");
   1.174 +  applyEnsureNoFailures([
   1.175 +    {id: resguid,
   1.176 +     histUri: resuri.spec,
   1.177 +     title: null,
   1.178 +     visits: [{date: TIMESTAMP3,
   1.179 +               type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
   1.180 +  ]);
   1.181 +  do_check_attribute_count(store.getAllIDs(), 3);
   1.182 +  let queryres = queryHistoryVisits(resuri);
   1.183 +  do_check_eq(queryres.length, 1);
   1.184 +  do_check_eq(queryres[0].time, TIMESTAMP3);
   1.185 +  run_next_test();
   1.186 +});
   1.187 +
   1.188 +add_test(function test_invalid_records() {
   1.189 +  _("Make sure we handle invalid URLs in places databases gracefully.");
   1.190 +  let connection = PlacesUtils.history
   1.191 +                              .QueryInterface(Ci.nsPIPlacesDatabase)
   1.192 +                              .DBConnection;
   1.193 +  let stmt = connection.createAsyncStatement(
   1.194 +    "INSERT INTO moz_places "
   1.195 +  + "(url, title, rev_host, visit_count, last_visit_date) "
   1.196 +  + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
   1.197 +  );
   1.198 +  Async.querySpinningly(stmt);
   1.199 +  stmt.finalize();
   1.200 +  // Add the corresponding visit to retain database coherence.
   1.201 +  stmt = connection.createAsyncStatement(
   1.202 +    "INSERT INTO moz_historyvisits "
   1.203 +  + "(place_id, visit_date, visit_type, session) "
   1.204 +  + "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), "
   1.205 +  + TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)"
   1.206 +  );
   1.207 +  Async.querySpinningly(stmt);
   1.208 +  stmt.finalize();
   1.209 +  do_check_attribute_count(store.getAllIDs(), 4);
   1.210 +
   1.211 +  _("Make sure we report records with invalid URIs.");
   1.212 +  let invalid_uri_guid = Utils.makeGUID();
   1.213 +  let failed = store.applyIncomingBatch([{
   1.214 +    id: invalid_uri_guid,
   1.215 +    histUri: ":::::::::::::::",
   1.216 +    title: "Doesn't have a valid URI",
   1.217 +    visits: [{date: TIMESTAMP3,
   1.218 +              type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
   1.219 +  ]);
   1.220 +  do_check_eq(failed.length, 1);
   1.221 +  do_check_eq(failed[0], invalid_uri_guid);
   1.222 +
   1.223 +  _("Make sure we handle records with invalid GUIDs gracefully (ignore).");
   1.224 +  applyEnsureNoFailures([
   1.225 +    {id: "invalid",
   1.226 +     histUri: "http://invalid.guid/",
   1.227 +     title: "Doesn't have a valid GUID",
   1.228 +     visits: [{date: TIMESTAMP3,
   1.229 +               type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
   1.230 +  ]);
   1.231 +
   1.232 +  _("Make sure we report records with invalid visits, gracefully handle non-integer dates.");
   1.233 +  let no_date_visit_guid = Utils.makeGUID();
   1.234 +  let no_type_visit_guid = Utils.makeGUID();
   1.235 +  let invalid_type_visit_guid = Utils.makeGUID();
   1.236 +  let non_integer_visit_guid = Utils.makeGUID();
   1.237 +  failed = store.applyIncomingBatch([
   1.238 +    {id: no_date_visit_guid,
   1.239 +     histUri: "http://no.date.visit/",
   1.240 +     title: "Visit has no date",
   1.241 +     visits: [{date: TIMESTAMP3}]},
   1.242 +    {id: no_type_visit_guid,
   1.243 +     histUri: "http://no.type.visit/",
   1.244 +     title: "Visit has no type",
   1.245 +     visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
   1.246 +    {id: invalid_type_visit_guid,
   1.247 +     histUri: "http://invalid.type.visit/",
   1.248 +     title: "Visit has invalid type",
   1.249 +     visits: [{date: TIMESTAMP3,
   1.250 +               type: Ci.nsINavHistoryService.TRANSITION_LINK - 1}]},
   1.251 +    {id: non_integer_visit_guid,
   1.252 +     histUri: "http://non.integer.visit/",
   1.253 +     title: "Visit has non-integer date",
   1.254 +     visits: [{date: 1234.567,
   1.255 +               type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
   1.256 +  ]);
   1.257 +  do_check_eq(failed.length, 3);
   1.258 +  failed.sort();
   1.259 +  let expected = [no_date_visit_guid,
   1.260 +                  no_type_visit_guid,
   1.261 +                  invalid_type_visit_guid].sort();
   1.262 +  for (let i = 0; i < expected.length; i++) {
   1.263 +    do_check_eq(failed[i], expected[i]);
   1.264 +  }
   1.265 +
   1.266 +  _("Make sure we handle records with javascript: URLs gracefully.");
   1.267 +  applyEnsureNoFailures([
   1.268 +    {id: Utils.makeGUID(),
   1.269 +     histUri: "javascript:''",
   1.270 +     title: "javascript:''",
   1.271 +     visits: [{date: TIMESTAMP3,
   1.272 +               type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
   1.273 +  ]);
   1.274 +
   1.275 +  _("Make sure we handle records without any visits gracefully.");
   1.276 +  applyEnsureNoFailures([
   1.277 +    {id: Utils.makeGUID(),
   1.278 +     histUri: "http://getfirebug.com",
   1.279 +     title: "Get Firebug!",
   1.280 +     visits: []}
   1.281 +  ]);
   1.282 +
   1.283 +  run_next_test();
   1.284 +});
   1.285 +
   1.286 +add_test(function test_remove() {
   1.287 +  _("Remove an existent record and a non-existent from the store.");
   1.288 +  applyEnsureNoFailures([{id: fxguid, deleted: true},
   1.289 +                         {id: Utils.makeGUID(), deleted: true}]);
   1.290 +  do_check_false(store.itemExists(fxguid));
   1.291 +  let queryres = queryHistoryVisits(fxuri);
   1.292 +  do_check_eq(queryres.length, 0);
   1.293 +
   1.294 +  _("Make sure wipe works.");
   1.295 +  store.wipe();
   1.296 +  do_check_empty(store.getAllIDs());
   1.297 +  queryres = queryHistoryVisits(fxuri);
   1.298 +  do_check_eq(queryres.length, 0);
   1.299 +  queryres = queryHistoryVisits(tburi);
   1.300 +  do_check_eq(queryres.length, 0);
   1.301 +  run_next_test();
   1.302 +});
   1.303 +
   1.304 +add_test(function cleanup() {
   1.305 +  _("Clean up.");
   1.306 +  PlacesUtils.history.removeAllPages();
   1.307 +  run_next_test();
   1.308 +});

mercurial