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