tor-browser

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

test_AdsFeed.js (16622B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  AdsFeed: "resource://newtab/lib/AdsFeed.sys.mjs",
      8  actionCreators: "resource://newtab/common/Actions.mjs",
      9  actionTypes: "resource://newtab/common/Actions.mjs",
     10  sinon: "resource://testing-common/Sinon.sys.mjs",
     11 });
     12 
     13 const { ObliviousHTTP } = ChromeUtils.importESModule(
     14  "resource://gre/modules/ObliviousHTTP.sys.mjs"
     15 );
     16 
     17 // Prefs to enable UAPI ad types
     18 const PREF_UNIFIED_ADS_ADSFEED_ENABLED = "unifiedAds.adsFeed.enabled";
     19 const PREF_UNIFIED_ADS_TILES_ENABLED = "unifiedAds.tiles.enabled";
     20 const PREF_UNIFIED_ADS_SPOCS_ENABLED = "unifiedAds.spocs.enabled";
     21 const PREF_UNIFIED_ADS_ENDPOINT = "unifiedAds.endpoint";
     22 const PREF_TILES_PLACEMENTS = "discoverystream.placements.tiles";
     23 const PREF_TILES_COUNTS = "discoverystream.placements.tiles.counts";
     24 const PREF_SPOC_COUNTS = "discoverystream.placements.spocs.counts";
     25 const PREF_SPOC_PLACEMENTS = "discoverystream.placements.spocs";
     26 const PREF_UNIFIED_ADS_BLOCKED_LIST = "unifiedAds.blockedAds";
     27 
     28 // Note: Full pref path required by Services.prefs.setBoolPref
     29 const PREF_UNIFIED_ADS_OHTTP_ENABLED =
     30  "browser.newtabpage.activity-stream.unifiedAds.ohttp.enabled";
     31 const PREF_UNIFIED_ADS_OHTTP_RELAY_URL =
     32  "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL";
     33 const PREF_UNIFIED_ADS_OHTTP_CONFIG_URL =
     34  "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL";
     35 
     36 // Primary pref that is toggled when enabling top site sponsored tiles
     37 const PREF_FEED_TOPSITES = "feeds.topsites";
     38 const PREF_SYSTEM_TOPSITES = "feeds.system.topsites";
     39 const PREF_SHOW_SPONSORED_TOPSITES = "showSponsoredTopSites";
     40 
     41 // Primary pref that is toggled when enabling sponsored stories
     42 const PREF_FEED_SECTIONS_TOPSTORIES = "feeds.section.topstories";
     43 const PREF_SYSTEM_TOPSTORIES = "feeds.system.topstories";
     44 const PREF_SHOW_SPONSORED = "showSponsored";
     45 const PREF_SYSTEM_SHOW_SPONSORED = "system.showSponsored";
     46 
     47 const mockedTileData = [
     48  {
     49    url: "https://www.test.com",
     50    image_url: "images/test-com.png",
     51    click_url: "https://www.test-click.com",
     52    impression_url: "https://www.test-impression.com",
     53    name: "test",
     54  },
     55  {
     56    url: "https://www.test1.com",
     57    image_url: "images/test1-com.png",
     58    click_url: "https://www.test1-click.com",
     59    impression_url: "https://www.test1-impression.com",
     60    name: "test1",
     61  },
     62 ];
     63 
     64 const mockedFetchTileData = {
     65  newtab_tile_1: [
     66    {
     67      format: "tile",
     68      url: "https://www.test1.com",
     69      callbacks: {
     70        click: "https://www.test1-click.com",
     71        impression: "https://www.test1-impression.com",
     72        report: "https://www.test1-report.com",
     73      },
     74      image_url: "images/test1-com.png",
     75      name: "test1",
     76      block_key: "test1",
     77    },
     78  ],
     79  newtab_tile_2: [
     80    {
     81      format: "tile",
     82      url: "https://www.test2.com",
     83      callbacks: {
     84        click: "https://www.test2-click.com",
     85        impression: "https://www.test2-impression.com",
     86        report: "https://www.test2-report.com",
     87      },
     88      image_url: "images/test2-com.png",
     89      name: "test2",
     90      block_key: "test2",
     91    },
     92  ],
     93  newtab_tile_3: [
     94    {
     95      format: "tile",
     96      url: "https://www.test3.com",
     97      callbacks: {
     98        click: "https://www.test3-click.com",
     99        impression: "https://www.test3-impression.com",
    100        report: "https://www.test3-report.com",
    101      },
    102      image_url: "images/test3-com.png",
    103      name: "test3",
    104      block_key: "test3",
    105    },
    106  ],
    107 };
    108 
    109 const mockedSpocPlacements = [
    110  {
    111    placement: "newtab_spocs",
    112    count: 6,
    113  },
    114 ];
    115 
    116 const mockedSpocsData = [
    117  {
    118    format: "spoc",
    119    url: "https://www.test.com",
    120    callbacks: {
    121      click: "https://www.test-click.com",
    122      impression: "https://www.test-impression.com",
    123      report: "https://www.test-report.com",
    124    },
    125    image_url: "images/test-com.png",
    126    title: "test",
    127    domain: "test.com",
    128    excerpt: "Lorem ipsum dolor sit amet.",
    129    sponsor: "Test",
    130    block_key: "test",
    131    caps: {
    132      cap_key: "123456789",
    133      day: 10,
    134    },
    135    ranking: {
    136      priority: 9,
    137      personalization_models: {},
    138      item_score: 1,
    139    },
    140  },
    141  {
    142    format: "spoc",
    143    url: "https://www.test1.com",
    144    callbacks: {
    145      click: "https://www.test1-click.com",
    146      impression: "https://www.test1-impression.com",
    147      report: "https://www.test1-report.com",
    148    },
    149    image_url: "images/test1-com.png",
    150    title: "test1",
    151    domain: "test1.com",
    152    excerpt: "Lorem ipsum dolor sit amet.",
    153    sponsor: "Test1",
    154    block_key: "test1",
    155    caps: {
    156      cap_key: "234567891",
    157      day: 10,
    158    },
    159    ranking: {
    160      priority: 9,
    161      personalization_models: {},
    162      item_score: 1,
    163    },
    164  },
    165  {
    166    format: "spoc",
    167    url: "https://www.test2.com",
    168    callbacks: {
    169      click: "https://www.test2-click.com",
    170      impression: "https://www.test2-impression.com",
    171      report: "https://www.test2-report.com",
    172    },
    173    image_url: "images/test2-com.png",
    174    title: "test2",
    175    domain: "test2.com",
    176    excerpt: "Lorem ipsum dolor sit amet.",
    177    sponsor: "Test2",
    178    block_key: "test2",
    179    caps: {
    180      cap_key: "345678912",
    181      day: 10,
    182    },
    183    ranking: {
    184      priority: 9,
    185      personalization_models: {},
    186      item_score: 1,
    187    },
    188  },
    189  {
    190    format: "spoc",
    191    url: "https://www.test3.com",
    192    callbacks: {
    193      click: "https://www.test3-click.com",
    194      impression: "https://www.test3-impression.com",
    195      report: "https://www.test3-report.com",
    196    },
    197    image_url: "images/test3-com.png",
    198    title: "test3",
    199    domain: "test3.com",
    200    excerpt: "Lorem ipsum dolor sit amet.",
    201    sponsor: "Test3",
    202    block_key: "test3",
    203    caps: {
    204      cap_key: "456789123",
    205      day: 10,
    206    },
    207    ranking: {
    208      priority: 9,
    209      personalization_models: {},
    210      item_score: 1,
    211    },
    212  },
    213  {
    214    format: "spoc",
    215    url: "https://www.test4.com",
    216    callbacks: {
    217      click: "https://www.test4-click.com",
    218      impression: "https://www.test4-impression.com",
    219      report: "https://www.test4-report.com",
    220    },
    221    image_url: "images/test4-com.png",
    222    title: "test4",
    223    domain: "test4.com",
    224    excerpt: "Lorem ipsum dolor sit amet.",
    225    sponsor: "Test4",
    226    block_key: "test4",
    227    caps: {
    228      cap_key: "567891234",
    229      day: 10,
    230    },
    231    ranking: {
    232      priority: 9,
    233      personalization_models: {},
    234      item_score: 1,
    235    },
    236  },
    237  {
    238    format: "spoc",
    239    url: "https://www.test5.com",
    240    callbacks: {
    241      click: "https://www.test5-click.com",
    242      impression: "https://www.test5-impression.com",
    243      report: "https://www.test5-report.com",
    244    },
    245    image_url: "images/test5-com.png",
    246    title: "test5",
    247    domain: "test5.com",
    248    excerpt: "Lorem ipsum dolor sit amet.",
    249    sponsor: "Test5",
    250    block_key: "test5",
    251    caps: {
    252      cap_key: "678912345",
    253      day: 10,
    254    },
    255    ranking: {
    256      priority: 9,
    257      personalization_models: {},
    258      item_score: 1,
    259    },
    260  },
    261 ];
    262 
    263 function getAdsFeedForTest() {
    264  let feed = new AdsFeed();
    265  let tiles = mockedTileData;
    266  let spocs = mockedSpocsData;
    267  let spocPlacements = mockedSpocPlacements;
    268 
    269  feed.store = {
    270    dispatch: sinon.spy(),
    271    getState() {
    272      return this.state;
    273    },
    274    state: {
    275      tiles,
    276      spocs,
    277      spocPlacements,
    278      lastUpdated: 1,
    279      Prefs: {
    280        values: {
    281          [PREF_TILES_PLACEMENTS]:
    282            "newtab_tile_1, newtab_tile_2, newtab_tile_3",
    283          [PREF_TILES_COUNTS]: "1, 1, 1",
    284          [PREF_SPOC_PLACEMENTS]: "newtab_spocs",
    285          [PREF_SPOC_COUNTS]: "6",
    286          [PREF_UNIFIED_ADS_BLOCKED_LIST]: "",
    287          [PREF_UNIFIED_ADS_ENDPOINT]: "https://example.com/",
    288          // AdsFeed/UAPI specific prefs to test
    289          [PREF_UNIFIED_ADS_TILES_ENABLED]: false,
    290          [PREF_UNIFIED_ADS_SPOCS_ENABLED]: false,
    291          [PREF_UNIFIED_ADS_ADSFEED_ENABLED]: false,
    292          // Default display prefs for tiles/spocs
    293          [PREF_FEED_TOPSITES]: true,
    294          [PREF_SYSTEM_TOPSITES]: true,
    295          [PREF_SHOW_SPONSORED_TOPSITES]: true,
    296          [PREF_FEED_SECTIONS_TOPSTORIES]: true,
    297          [PREF_SYSTEM_TOPSTORIES]: true,
    298          [PREF_SHOW_SPONSORED]: true,
    299          [PREF_SYSTEM_SHOW_SPONSORED]: true,
    300        },
    301      },
    302    },
    303  };
    304 
    305  return feed;
    306 }
    307 
    308 add_task(async function test_construction() {
    309  let sandbox = sinon.createSandbox();
    310  sandbox.stub(AdsFeed.prototype, "PersistentCache").returns({
    311    set: () => {},
    312    get: () => {},
    313  });
    314 
    315  let feed = new AdsFeed();
    316 
    317  info("AdsFeed constructor should create initial values");
    318 
    319  Assert.ok(feed, "Could construct a AdsFeed");
    320  Assert.strictEqual(feed.loaded, false, "AdsFeed is not loaded");
    321  Assert.strictEqual(feed.enabled, false, "AdsFeed is not enabled");
    322  Assert.strictEqual(
    323    feed.lastUpdated,
    324    null,
    325    "AdsFeed has no lastUpdated record"
    326  );
    327  Assert.strictEqual(
    328    feed.tiles.length,
    329    0,
    330    "tiles is initialized as a array with length of 0"
    331  );
    332  sandbox.restore();
    333 });
    334 
    335 add_task(async function test_isEnabled_tiles() {
    336  let sandbox = sinon.createSandbox();
    337  sandbox.stub(AdsFeed.prototype, "PersistentCache").returns({
    338    set: () => {},
    339    get: () => {},
    340  });
    341  const dateNowTestValue = 1;
    342  sandbox.stub(AdsFeed.prototype, "Date").returns({
    343    now: () => dateNowTestValue,
    344  });
    345 
    346  let feed = getAdsFeedForTest(sandbox);
    347 
    348  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_ADSFEED_ENABLED] = true;
    349  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_TILES_ENABLED] = true;
    350  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_SPOCS_ENABLED] = true;
    351  feed.store.state.Prefs.values[PREF_FEED_TOPSITES] = true;
    352  feed.store.state.Prefs.values[PREF_SYSTEM_TOPSITES] = true;
    353  feed.store.state.Prefs.values[PREF_SHOW_SPONSORED_TOPSITES] = true;
    354  feed.store.state.Prefs.values[PREF_FEED_SECTIONS_TOPSTORIES] = true;
    355  feed.store.state.Prefs.values[PREF_SYSTEM_TOPSTORIES] = true;
    356  feed.store.state.Prefs.values[PREF_SHOW_SPONSORED] = true;
    357  feed.store.state.Prefs.values[PREF_SYSTEM_SHOW_SPONSORED] = true;
    358 
    359  Assert.ok(feed.isEnabled());
    360 
    361  sandbox.restore();
    362 });
    363 
    364 add_task(async function test_onAction_INIT_tiles_and_spocs() {
    365  let sandbox = sinon.createSandbox();
    366  sandbox.stub(AdsFeed.prototype, "PersistentCache").returns({
    367    set: () => {},
    368    get: () => {},
    369  });
    370  const dateNowTestValue = 1;
    371  sandbox.stub(AdsFeed.prototype, "Date").returns({
    372    now: () => dateNowTestValue,
    373  });
    374 
    375  let feed = getAdsFeedForTest(sandbox);
    376 
    377  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_ADSFEED_ENABLED] = true;
    378  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_TILES_ENABLED] = true;
    379  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_SPOCS_ENABLED] = true;
    380 
    381  sandbox.stub(feed, "isEnabled").returns(true);
    382 
    383  sandbox.stub(feed, "fetchData").returns({
    384    tiles: mockedTileData,
    385    spocs: mockedSpocsData,
    386    spocPlacements: mockedSpocPlacements,
    387    lastUpdated: dateNowTestValue,
    388  });
    389 
    390  info("AdsFeed.onAction INIT should initialize Ads");
    391 
    392  await feed.onAction({
    393    type: actionTypes.INIT,
    394  });
    395 
    396  Assert.ok(feed.store.dispatch.calledTwice);
    397 
    398  Assert.ok(
    399    feed.store.dispatch.firstCall.calledWithExactly(
    400      actionCreators.BroadcastToContent({
    401        type: "ADS_UPDATE_TILES",
    402        data: {
    403          tiles: mockedTileData,
    404        },
    405        meta: {
    406          isStartup: true,
    407        },
    408      })
    409    )
    410  );
    411 
    412  Assert.ok(
    413    feed.store.dispatch.secondCall.calledWithExactly(
    414      actionCreators.BroadcastToContent({
    415        type: "ADS_UPDATE_SPOCS",
    416        data: {
    417          spocs: mockedSpocsData,
    418          spocPlacements: mockedSpocPlacements,
    419        },
    420        meta: {
    421          isStartup: true,
    422        },
    423      })
    424    )
    425  );
    426 
    427  sandbox.restore();
    428 });
    429 
    430 add_task(async function test_onAction_INIT_spocs_only() {
    431  let sandbox = sinon.createSandbox();
    432  sandbox.stub(AdsFeed.prototype, "PersistentCache").returns({
    433    set: () => {},
    434    get: () => {},
    435  });
    436  const dateNowTestValue = 1;
    437  sandbox.stub(AdsFeed.prototype, "Date").returns({
    438    now: () => dateNowTestValue,
    439  });
    440 
    441  let feed = getAdsFeedForTest(sandbox);
    442 
    443  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_ADSFEED_ENABLED] = true;
    444  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_SPOCS_ENABLED] = true;
    445 
    446  sandbox.stub(feed, "isEnabled").returns(true);
    447 
    448  sandbox.stub(feed, "fetchData").returns({
    449    tiles: [],
    450    spocs: mockedSpocsData,
    451    spocPlacements: mockedSpocPlacements,
    452    lastUpdated: dateNowTestValue,
    453  });
    454 
    455  info(
    456    "AdsFeed.onAction INIT should return only spocs when tiles are disabled"
    457  );
    458 
    459  await feed.onAction({
    460    type: actionTypes.INIT,
    461  });
    462 
    463  Assert.ok(feed.store.dispatch.calledOnce);
    464  Assert.ok(
    465    feed.store.dispatch.calledWithExactly(
    466      actionCreators.BroadcastToContent({
    467        type: "ADS_UPDATE_SPOCS",
    468        data: {
    469          spocs: mockedSpocsData,
    470          spocPlacements: mockedSpocPlacements,
    471        },
    472        meta: {
    473          isStartup: true,
    474        },
    475      })
    476    )
    477  );
    478 
    479  sandbox.restore();
    480 });
    481 
    482 add_task(async function test_onAction_INIT_tiles_only() {
    483  let sandbox = sinon.createSandbox();
    484  sandbox.stub(AdsFeed.prototype, "PersistentCache").returns({
    485    set: () => {},
    486    get: () => {},
    487  });
    488  const dateNowTestValue = 1;
    489  sandbox.stub(AdsFeed.prototype, "Date").returns({
    490    now: () => dateNowTestValue,
    491  });
    492 
    493  let feed = getAdsFeedForTest(sandbox);
    494 
    495  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_ADSFEED_ENABLED] = true;
    496  feed.store.state.Prefs.values[PREF_UNIFIED_ADS_TILES_ENABLED] = true;
    497 
    498  sandbox.stub(feed, "isEnabled").returns(true);
    499 
    500  sandbox.stub(feed, "fetchData").returns({
    501    tiles: mockedTileData,
    502    spocs: [],
    503    spocPlacements: [],
    504    lastUpdated: dateNowTestValue,
    505  });
    506 
    507  info(
    508    "AdsFeed.onAction INIT should return only tiles when spocs are disabled"
    509  );
    510 
    511  await feed.onAction({
    512    type: actionTypes.INIT,
    513  });
    514 
    515  Assert.ok(feed.store.dispatch.calledOnce);
    516  Assert.ok(
    517    feed.store.dispatch.calledWithExactly(
    518      actionCreators.BroadcastToContent({
    519        type: "ADS_UPDATE_TILES",
    520        data: {
    521          tiles: mockedTileData,
    522        },
    523        meta: {
    524          isStartup: true,
    525        },
    526      })
    527    )
    528  );
    529 
    530  sandbox.restore();
    531 });
    532 
    533 add_task(async function test_fetchData_noOHTTP() {
    534  const sandbox = sinon.createSandbox();
    535  const feed = getAdsFeedForTest();
    536 
    537  sandbox
    538    .stub(AdsFeed.prototype, "PersistentCache")
    539    .returns({ get: () => {}, set: () => {} });
    540  sandbox.stub(feed, "fetch").resolves(
    541    new Response(
    542      JSON.stringify({
    543        tile1: [
    544          {
    545            block_key: "foo",
    546            name: "bar",
    547            url: "https://test.com",
    548            image_url: "image.png",
    549            callbacks: { click: "click", impression: "impression" },
    550          },
    551        ],
    552      })
    553    )
    554  );
    555 
    556  sandbox.stub(feed, "Date").returns({ now: () => 123 });
    557 
    558  // Simulate OHTTP being disabled
    559  Services.prefs.setBoolPref(PREF_UNIFIED_ADS_OHTTP_ENABLED, false);
    560 
    561  const supportedAdTypes = { tiles: true, spocs: false };
    562  await feed.fetchData(supportedAdTypes);
    563 
    564  info("AdsFeed: fetchData() should fetch normally via HTTP");
    565 
    566  Assert.ok(feed.fetch.calledOnce, "Fallback fetch called");
    567  sandbox.restore();
    568 });
    569 
    570 add_task(async function test_fetchData_OHTTP() {
    571  const sandbox = sinon.createSandbox();
    572  const feed = getAdsFeedForTest();
    573 
    574  Services.prefs.setBoolPref(PREF_UNIFIED_ADS_OHTTP_ENABLED, true);
    575  Services.prefs.setStringPref(
    576    PREF_UNIFIED_ADS_OHTTP_RELAY_URL,
    577    "https://relay.test"
    578  );
    579  Services.prefs.setStringPref(
    580    PREF_UNIFIED_ADS_OHTTP_CONFIG_URL,
    581    "https://config.test"
    582  );
    583 
    584  const mockConfig = { config: "mocked" };
    585 
    586  sandbox
    587    .stub(AdsFeed.prototype, "PersistentCache")
    588    .returns({ get: () => {}, set: () => {} });
    589  sandbox.stub(feed, "Date").returns({ now: () => 123 });
    590 
    591  sandbox.stub(ObliviousHTTP, "getOHTTPConfig").resolves(mockConfig);
    592  sandbox.stub(ObliviousHTTP, "ohttpRequest").resolves({
    593    status: 200,
    594    json: () => {
    595      return Promise.resolve(mockedFetchTileData);
    596    },
    597  });
    598 
    599  const result = await feed.fetchData({ tiles: true, spocs: false });
    600 
    601  info("AdsFeed: fetchData() should fetch via OHTTP when enabled");
    602 
    603  Assert.ok(ObliviousHTTP.getOHTTPConfig.calledOnce);
    604  Assert.ok(ObliviousHTTP.ohttpRequest.calledOnce);
    605  Assert.deepEqual(result.tiles[0].id, "test1");
    606 
    607  info("AdsFeed: fetchData() should not send cookies");
    608  Assert.equal(
    609    ObliviousHTTP.ohttpRequest.firstCall.args[3].credentials,
    610    "omit",
    611    "should not send cookies"
    612  );
    613 
    614  sandbox.restore();
    615 });