services/sync/tests/unit/test_history_store.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 });

mercurial