tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_SearchBrowsingHistory.js (9338B)


      1 /**
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
      5 */
      6 
      7 const { searchBrowsingHistory } = ChromeUtils.importESModule(
      8  "moz-src:///browser/components/aiwindow/models/SearchBrowsingHistory.sys.mjs"
      9 );
     10 
     11 const { sinon } = ChromeUtils.importESModule(
     12  "resource://testing-common/Sinon.sys.mjs"
     13 );
     14 
     15 let sb;
     16 
     17 // setup
     18 add_task(async function setup() {
     19  sb = sinon.createSandbox();
     20  registerCleanupFunction(() => {
     21    sb.restore();
     22    Services.prefs.clearUserPref("browser.ml.enable");
     23    Services.prefs.clearUserPref("places.semanticHistory.featureGate");
     24    Services.prefs.clearUserPref("browser.search.region");
     25  });
     26 
     27  Services.prefs.setBoolPref("browser.ml.enable", true);
     28  Services.prefs.setBoolPref("places.semanticHistory.featureGate", true);
     29  Services.prefs.setCharPref("browser.search.region", "US");
     30 
     31  await PlacesUtils.history.clear();
     32 });
     33 
     34 // test: empty searchTerm, no time window
     35 add_task(async function test_basic_history_fetch_and_shape() {
     36  await PlacesUtils.history.clear();
     37 
     38  const now = Date.now();
     39 
     40  const seeded = [
     41    {
     42      url: "https://www.google.com/search?q=firefox+history",
     43      title: "Google Search: firefox history",
     44      visits: [{ date: new Date(now - 5 * 60 * 1000) }], // 5 min ago
     45    },
     46    {
     47      url: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
     48      title: "JavaScript | MDN",
     49      visits: [{ date: new Date(now - 10 * 60 * 1000) }], // 10 min ago
     50    },
     51    {
     52      url: "https://news.ycombinator.com/",
     53      title: "Hacker News",
     54      visits: [{ date: new Date(now - 15 * 60 * 1000) }],
     55    },
     56    {
     57      url: "https://search.brave.com/search?q=mozsqlite",
     58      title: "Brave Search: mozsqlite",
     59      visits: [{ date: new Date(now - 20 * 60 * 1000) }],
     60    },
     61    {
     62      url: "https://mozilla.org/en-US/",
     63      title: "Internet for people, not profit — Mozilla",
     64      visits: [{ date: new Date(now - 25 * 60 * 1000) }],
     65    },
     66  ];
     67 
     68  await PlacesUtils.history.insertMany(seeded);
     69 
     70  const allRowsStr = await searchBrowsingHistory({
     71    searchTerm: "",
     72    startTs: null,
     73    endTs: null,
     74    historyLimit: 15,
     75  });
     76  const allRowsObj = JSON.parse(allRowsStr);
     77 
     78  // check count match
     79  Assert.equal(
     80    allRowsObj.count,
     81    seeded.length,
     82    "Should return all seeded records"
     83  );
     84 
     85  // check all url match
     86  const urls = allRowsObj.results.map(r => r.url).sort();
     87  const expectedUrls = seeded.map(s => s.url).sort();
     88  Assert.deepEqual(urls, expectedUrls, "Should return all seeded URLs");
     89 
     90  // check title and url match
     91  const byUrl = new Map(allRowsObj.results.map(r => [r.url, r]));
     92  for (const { url, title } of seeded) {
     93    Assert.ok(byUrl.has(url), `Has entry for ${url}`);
     94    Assert.equal(byUrl.get(url).title, title, `Title matches for ${url}`);
     95  }
     96 
     97  // check visitDate iso string
     98  for (const r of allRowsObj.results) {
     99    Assert.ok(
    100      !isNaN(Date.parse(r.visitDate)),
    101      "visitDate is a valid ISO timestamp"
    102    );
    103  }
    104 });
    105 
    106 // test: startTs only
    107 add_task(async function test_time_range_only_startTs() {
    108  await PlacesUtils.history.clear();
    109 
    110  const now = Date.now();
    111 
    112  const older = {
    113    url: "https://example.com/older",
    114    title: "Older Page",
    115    visits: [{ date: new Date(now - 60 * 60 * 1000) }], // 60 min ago
    116  };
    117  const recent = {
    118    url: "https://example.com/recent",
    119    title: "Recent Page",
    120    visits: [{ date: new Date(now - 5 * 60 * 1000) }], // 5 min ago
    121  };
    122 
    123  await PlacesUtils.history.insertMany([older, recent]);
    124 
    125  // records after last 10 minutes
    126  const startTs = new Date(now - 10 * 60 * 1000).toISOString(); // ISO input
    127 
    128  const rowsStr = await searchBrowsingHistory({
    129    searchTerm: "",
    130    startTs,
    131    endTs: null,
    132    historyLimit: 15,
    133  });
    134  const rows = JSON.parse(rowsStr);
    135  const urls = rows.results.map(r => r.url);
    136 
    137  Assert.ok(
    138    urls.includes(recent.url),
    139    "Recent entry should be included when only startTs is set"
    140  );
    141  Assert.ok(
    142    !urls.includes(older.url),
    143    "Older entry should be excluded when only startTs is set"
    144  );
    145 });
    146 
    147 // test: endTs only
    148 add_task(async function test_time_range_only_endTs() {
    149  await PlacesUtils.history.clear();
    150 
    151  const now = Date.now();
    152 
    153  const older = {
    154    url: "https://example.com/older",
    155    title: "Older Page",
    156    visits: [{ date: new Date(now - 60 * 60 * 1000) }], // 60 min ago
    157  };
    158  const recent = {
    159    url: "https://example.com/recent",
    160    title: "Recent Page",
    161    visits: [{ date: new Date(now - 5 * 60 * 1000) }], // 5 min ago
    162  };
    163 
    164  await PlacesUtils.history.insertMany([older, recent]);
    165 
    166  // Anything before last 10 minutes
    167  const endTs = new Date(now - 10 * 60 * 1000).toISOString(); // ISO input
    168 
    169  const rowsStr = await searchBrowsingHistory({
    170    searchTerm: "",
    171    startTs: null,
    172    endTs,
    173    historyLimit: 15,
    174  });
    175  const rows = JSON.parse(rowsStr);
    176  const urls = rows.results.map(r => r.url);
    177 
    178  Assert.ok(
    179    urls.includes(older.url),
    180    "Older entry should be included when only endTs is set"
    181  );
    182  Assert.ok(
    183    !urls.includes(recent.url),
    184    "Recent entry should be excluded when only endTs is set"
    185  );
    186 });
    187 
    188 // test: startTs + endTs
    189 add_task(async function test_time_range_start_and_endTs() {
    190  await PlacesUtils.history.clear();
    191 
    192  const now = Date.now();
    193 
    194  const beforeWindow = {
    195    url: "https://example.com/before-window",
    196    title: "Before Window",
    197    visits: [{ date: new Date(now - 3 * 60 * 60 * 1000) }], // 3h ago
    198  };
    199  const inWindow = {
    200    url: "https://example.com/in-window",
    201    title: "In Window",
    202    visits: [{ date: new Date(now - 30 * 60 * 1000) }], // 30 min ago
    203  };
    204  const afterWindow = {
    205    url: "https://example.com/after-window",
    206    title: "After Window",
    207    visits: [{ date: new Date(now - 5 * 60 * 1000) }], // 5 min ago
    208  };
    209 
    210  await PlacesUtils.history.insertMany([beforeWindow, inWindow, afterWindow]);
    211 
    212  // Time window: [45min ago,  15min ago]
    213  const startTs = new Date(now - 45 * 60 * 1000).toISOString();
    214  const endTs = new Date(now - 15 * 60 * 1000).toISOString();
    215 
    216  const rowsStr = await searchBrowsingHistory({
    217    searchTerm: "",
    218    startTs,
    219    endTs,
    220    historyLimit: 15,
    221  });
    222  const rows = JSON.parse(rowsStr);
    223  const urls = rows.results.map(r => r.url);
    224 
    225  Assert.ok(urls.includes(inWindow.url), "In window entry should be included");
    226  Assert.ok(
    227    !urls.includes(beforeWindow.url),
    228    "Before window entry should be excluded"
    229  );
    230  Assert.ok(
    231    !urls.includes(afterWindow.url),
    232    "After window entry should be excluded"
    233  );
    234 });
    235 
    236 /**
    237 * Test no results behavior: empty history with and without searchTerm.
    238 *
    239 * We don't try to force the semantic here (that would require a
    240 * running ML engine). Instead we just assert the wrapper's messaging
    241 * when there are no rows.
    242 */
    243 add_task(async function test_no_results_messages() {
    244  await PlacesUtils.history.clear();
    245 
    246  // No search term: time range message.
    247  let outputStr = await searchBrowsingHistory({
    248    searchTerm: "",
    249    startTs: null,
    250    endTs: null,
    251    historyLimit: 15,
    252  });
    253  let output = JSON.parse(outputStr);
    254 
    255  Assert.equal(output.results.length, 0, "No results when history is empty");
    256  Assert.ok(
    257    output.message.includes("requested time range"),
    258    "Message explains empty time-range search"
    259  );
    260 
    261  // With search term: search specific message.
    262  outputStr = await searchBrowsingHistory({
    263    searchTerm: "mozilla",
    264    startTs: null,
    265    endTs: null,
    266    historyLimit: 15,
    267  });
    268  output = JSON.parse(outputStr);
    269 
    270  Assert.equal(output.results.length, 0, "No results for semantic search");
    271  Assert.ok(
    272    output.message.includes("mozilla"),
    273    "Message mentions the search term when there are no matches"
    274  );
    275 });
    276 
    277 // test: non-empty searchTerm falls back to basic history search
    278 // when semantic search is disabled via prefs.
    279 add_task(async function test_basic_text_search_when_semantic_disabled() {
    280  await PlacesUtils.history.clear();
    281 
    282  const now = Date.now();
    283 
    284  const seeded = [
    285    {
    286      url: "https://www.mozilla.org/en-US/",
    287      title: "Internet for people, not profit — Mozilla",
    288      visits: [{ date: new Date(now - 5 * 60 * 1000) }], // 5 min ago
    289    },
    290    {
    291      url: "https://example.com/other",
    292      title: "Some Other Site",
    293      visits: [{ date: new Date(now - 10 * 60 * 1000) }], // 10 min ago
    294    },
    295  ];
    296 
    297  await PlacesUtils.history.insertMany(seeded);
    298 
    299  // Disable semantic search so searchBrowsingHistory must fall back
    300  // to the basic history search.
    301  Services.prefs.setBoolPref("browser.ml.enable", false);
    302  Services.prefs.setBoolPref("places.semanticHistory.featureGate", false);
    303 
    304  const outputStr = await searchBrowsingHistory({
    305    searchTerm: "mozilla",
    306    startTs: null,
    307    endTs: null,
    308    historyLimit: 15,
    309  });
    310  const output = JSON.parse(outputStr);
    311 
    312  Assert.equal(output.searchTerm, "mozilla", "searchTerm match");
    313  Assert.equal(output.results.length, 1, "One history entry is returned");
    314 
    315  const urls = output.results.map(r => r.url);
    316  Assert.ok(
    317    urls.includes("https://www.mozilla.org/en-US/"),
    318    "Basic history search should find the Mozilla entry"
    319  );
    320 
    321  // Restore prefs
    322  Services.prefs.setBoolPref("browser.ml.enable", true);
    323  Services.prefs.setBoolPref("places.semanticHistory.featureGate", true);
    324 });