test_ppa.js (21407B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { HttpServer } = ChromeUtils.importESModule( 7 "resource://testing-common/httpd.sys.mjs" 8 ); 9 10 const { PrivateAttributionService } = ChromeUtils.importESModule( 11 "resource://gre/modules/PrivateAttributionService.sys.mjs" 12 ); 13 14 const { AppConstants } = ChromeUtils.importESModule( 15 "resource://gre/modules/AppConstants.sys.mjs" 16 ); 17 18 const BinaryInputStream = Components.Constructor( 19 "@mozilla.org/binaryinputstream;1", 20 "nsIBinaryInputStream", 21 "setInputStream" 22 ); 23 24 const PREF_LEADER = "toolkit.telemetry.dap.leader.url"; 25 const PREF_HELPER = "toolkit.telemetry.dap.helper.url"; 26 const TASK_ID = "DSZGMFh26hBYXNaKvhL_N4AHA3P5lDn19on1vFPBxJM"; 27 const MAX_CONVERSIONS = 2; 28 const DAY_IN_MILLI = 1000 * 60 * 60 * 24; 29 const LOOKBACK_DAYS = 1; 30 const HISTOGRAM_SIZE = 5; 31 32 class MockDateProvider { 33 constructor() { 34 this._now = Date.now(); 35 } 36 37 now() { 38 return this._now; 39 } 40 41 add(interval_ms) { 42 this._now += interval_ms; 43 } 44 } 45 46 class MockDAPTelemetrySender { 47 constructor() { 48 this.receivedMeasurements = []; 49 } 50 51 async sendDAPMeasurement(task, measurement, { timeout }) { 52 this.receivedMeasurements.push({ 53 task, 54 measurement, 55 timeout, 56 }); 57 } 58 } 59 60 class MockServer { 61 constructor() { 62 this.receivedReports = []; 63 64 const server = new HttpServer(); 65 66 server.registerPrefixHandler( 67 "/leader_endpoint/tasks/", 68 this.uploadHandler.bind(this) 69 ); 70 71 this._server = server; 72 } 73 74 start() { 75 this._server.start(-1); 76 77 this.orig_leader = Services.prefs.getStringPref(PREF_LEADER); 78 this.orig_helper = Services.prefs.getStringPref(PREF_HELPER); 79 80 const i = this._server.identity; 81 const serverAddr = 82 i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort; 83 Services.prefs.setStringPref(PREF_LEADER, serverAddr + "/leader_endpoint"); 84 Services.prefs.setStringPref(PREF_HELPER, serverAddr + "/helper_endpoint"); 85 } 86 87 async stop() { 88 Services.prefs.setStringPref(PREF_LEADER, this.orig_leader); 89 Services.prefs.setStringPref(PREF_HELPER, this.orig_helper); 90 91 await this._server.stop(); 92 } 93 94 uploadHandler(request, response) { 95 let body = new BinaryInputStream(request.bodyInputStream); 96 97 this.receivedReports.push({ 98 contentType: request.getHeader("Content-Type"), 99 size: body.available(), 100 }); 101 102 response.setStatusLine(request.httpVersion, 200); 103 } 104 } 105 106 add_setup(async function () { 107 do_get_profile(); 108 Services.fog.initializeFOG(); 109 }); 110 111 add_task(async function testIsEnabled() { 112 Services.fog.testResetFOG(); 113 114 // isEnabled should match the telemetry preference without the test flag 115 const ppa1 = new PrivateAttributionService(); 116 Assert.equal( 117 ppa1.isEnabled(), 118 AppConstants.MOZ_TELEMETRY_REPORTING && 119 Services.prefs.getBoolPref("datareporting.healthreport.uploadEnabled") && 120 Services.prefs.getBoolPref("dom.private-attribution.submission.enabled") 121 ); 122 123 // Should always be enabled with the test flag 124 const ppa2 = new PrivateAttributionService({ testForceEnabled: true }); 125 Assert.ok(ppa2.isEnabled()); 126 }); 127 128 add_task(async function testSuccessfulConversion() { 129 Services.fog.testResetFOG(); 130 131 const mockSender = new MockDAPTelemetrySender(); 132 const privateAttribution = new PrivateAttributionService({ 133 dapTelemetrySender: mockSender, 134 testForceEnabled: true, 135 testDapOptions: { ohttp_relay: null }, 136 }); 137 138 const sourceHost = "source.test"; 139 const targetHost = "target.test"; 140 const adIdentifier = "ad_identifier"; 141 const adIndex = 1; 142 143 await privateAttribution.onAttributionEvent( 144 sourceHost, 145 "view", 146 adIndex, 147 adIdentifier, 148 targetHost 149 ); 150 151 await privateAttribution.onAttributionEvent( 152 sourceHost, 153 "click", 154 adIndex, 155 adIdentifier, 156 targetHost 157 ); 158 159 await privateAttribution.onAttributionConversion( 160 targetHost, 161 TASK_ID, 162 HISTOGRAM_SIZE, 163 LOOKBACK_DAYS, 164 "view", 165 [adIdentifier], 166 [sourceHost] 167 ); 168 169 const expectedMeasurement = { 170 task: { 171 id: TASK_ID, 172 vdaf: "sumvec", 173 bits: 8, 174 length: HISTOGRAM_SIZE, 175 time_precision: 60, 176 }, 177 measurement: [0, 1, 0, 0, 0], 178 timeout: 30000, 179 }; 180 181 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 182 Assert.deepEqual(receivedMeasurement, expectedMeasurement); 183 184 Assert.equal(mockSender.receivedMeasurements.length, 0); 185 }); 186 187 add_task(async function testImpressionsOnMultipleSites() { 188 Services.fog.testResetFOG(); 189 190 const mockSender = new MockDAPTelemetrySender(); 191 const mockDateProvider = new MockDateProvider(); 192 const privateAttribution = new PrivateAttributionService({ 193 dapTelemetrySender: mockSender, 194 dateProvider: mockDateProvider, 195 testForceEnabled: true, 196 testDapOptions: { ohttp_relay: null }, 197 }); 198 199 const sourceHost1 = "source-multiple-1.test"; 200 const adIndex1 = 1; 201 202 const sourceHost2 = "source-multiple-2.test"; 203 const adIndex2 = 3; 204 205 const targetHost = "target-multiple.test"; 206 const adIdentifier = "ad_identifier_multiple"; 207 208 // View at adIndex1 on sourceHost1 at t=0 209 await privateAttribution.onAttributionEvent( 210 sourceHost1, 211 "view", 212 adIndex1, 213 adIdentifier, 214 targetHost 215 ); 216 217 // Click at adIndex2 on sourceHost2 at t=1 218 mockDateProvider.add(1); 219 await privateAttribution.onAttributionEvent( 220 sourceHost2, 221 "click", 222 adIndex2, 223 adIdentifier, 224 targetHost 225 ); 226 227 // Click at adIndex1 on sourceHost1 at t=2 228 mockDateProvider.add(1); 229 await privateAttribution.onAttributionEvent( 230 sourceHost1, 231 "click", 232 adIndex1, 233 adIdentifier, 234 targetHost 235 ); 236 237 // View at adIndex2 on sourceHost2 at t=3 238 mockDateProvider.add(1); 239 await privateAttribution.onAttributionEvent( 240 sourceHost2, 241 "view", 242 adIndex2, 243 adIdentifier, 244 targetHost 245 ); 246 247 // Conversion for "click" matches most recent click on sourceHost1 248 await privateAttribution.onAttributionConversion( 249 targetHost, 250 TASK_ID, 251 HISTOGRAM_SIZE, 252 LOOKBACK_DAYS, 253 "click", 254 [adIdentifier], 255 [sourceHost1, sourceHost2] 256 ); 257 258 let receivedMeasurement = mockSender.receivedMeasurements.pop(); 259 Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]); 260 261 // Conversion for "view" matches most recent view on sourceHost2 262 await privateAttribution.onAttributionConversion( 263 targetHost, 264 TASK_ID, 265 HISTOGRAM_SIZE, 266 LOOKBACK_DAYS, 267 "view", 268 [adIdentifier], 269 [sourceHost1, sourceHost2] 270 ); 271 272 receivedMeasurement = mockSender.receivedMeasurements.pop(); 273 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]); 274 275 Assert.equal(mockSender.receivedMeasurements.length, 0); 276 }); 277 278 add_task(async function testConversionWithoutImpression() { 279 Services.fog.testResetFOG(); 280 281 const mockSender = new MockDAPTelemetrySender(); 282 const privateAttribution = new PrivateAttributionService({ 283 dapTelemetrySender: mockSender, 284 testForceEnabled: true, 285 testDapOptions: { ohttp_relay: null }, 286 }); 287 288 const sourceHost = "source-no-impression.test"; 289 const targetHost = "target-no-impression.test"; 290 const adIdentifier = "ad_identifier_no_impression"; 291 292 await privateAttribution.onAttributionConversion( 293 targetHost, 294 TASK_ID, 295 HISTOGRAM_SIZE, 296 LOOKBACK_DAYS, 297 "view", 298 [adIdentifier], 299 [sourceHost] 300 ); 301 302 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 303 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]); 304 305 Assert.equal(mockSender.receivedMeasurements.length, 0); 306 }); 307 308 add_task(async function testSelectionByInteractionType() { 309 Services.fog.testResetFOG(); 310 311 const mockSender = new MockDAPTelemetrySender(); 312 const privateAttribution = new PrivateAttributionService({ 313 dapTelemetrySender: mockSender, 314 testForceEnabled: true, 315 testDapOptions: { ohttp_relay: null }, 316 }); 317 318 const sourceHost = "source-by-type.test"; 319 const targetHost = "target-by-type.test"; 320 const adIdentifier = "ad_identifier_by_type"; 321 322 const viewAdIndex = 2; 323 const clickAdIndex = 3; 324 325 // View at viewAdIndex 326 await privateAttribution.onAttributionEvent( 327 sourceHost, 328 "view", 329 viewAdIndex, 330 adIdentifier, 331 targetHost 332 ); 333 334 // Click at clickAdIndex clobbers previous view at viewAdIndex 335 await privateAttribution.onAttributionEvent( 336 sourceHost, 337 "click", 338 clickAdIndex, 339 adIdentifier, 340 targetHost 341 ); 342 343 // Conversion filtering for "view" matches the interaction at clickAdIndex 344 await privateAttribution.onAttributionConversion( 345 targetHost, 346 TASK_ID, 347 HISTOGRAM_SIZE, 348 LOOKBACK_DAYS, 349 "view", 350 [adIdentifier], 351 [sourceHost] 352 ); 353 354 let receivedMeasurement = mockSender.receivedMeasurements.pop(); 355 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]); 356 357 Assert.equal(mockSender.receivedMeasurements.length, 0); 358 }); 359 360 add_task(async function testSelectionBySourceSite() { 361 Services.fog.testResetFOG(); 362 363 const mockSender = new MockDAPTelemetrySender(); 364 const privateAttribution = new PrivateAttributionService({ 365 dapTelemetrySender: mockSender, 366 testForceEnabled: true, 367 testDapOptions: { ohttp_relay: null }, 368 }); 369 370 const sourceHost1 = "source-by-site-1.test"; 371 const adIndex1 = 1; 372 373 const sourceHost2 = "source-by-site-2.test"; 374 const adIndex2 = 4; 375 376 const targetHost = "target-by-site.test"; 377 const adIdentifier = "ad_identifier_by_site"; 378 379 // Impression on sourceHost1 at adIndex1 380 await privateAttribution.onAttributionEvent( 381 sourceHost1, 382 "view", 383 adIndex1, 384 adIdentifier, 385 targetHost 386 ); 387 388 // Impression for the same ad ID on sourceHost2 at adIndex2 389 await privateAttribution.onAttributionEvent( 390 sourceHost2, 391 "view", 392 adIndex2, 393 adIdentifier, 394 targetHost 395 ); 396 397 // Conversion filtering for sourceHost1 matches impression at adIndex1 398 await privateAttribution.onAttributionConversion( 399 targetHost, 400 TASK_ID, 401 HISTOGRAM_SIZE, 402 LOOKBACK_DAYS, 403 "view", 404 [adIdentifier], 405 [sourceHost1] 406 ); 407 408 let receivedMeasurement = mockSender.receivedMeasurements.pop(); 409 Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]); 410 411 // Conversion filtering for sourceHost2 matches impression at adIndex2 412 await privateAttribution.onAttributionConversion( 413 targetHost, 414 TASK_ID, 415 HISTOGRAM_SIZE, 416 LOOKBACK_DAYS, 417 "view", 418 [adIdentifier], 419 [sourceHost2] 420 ); 421 422 receivedMeasurement = mockSender.receivedMeasurements.pop(); 423 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 1]); 424 425 Assert.equal(mockSender.receivedMeasurements.length, 0); 426 }); 427 428 add_task(async function testSelectionByAdIdentifier() { 429 Services.fog.testResetFOG(); 430 431 const mockSender = new MockDAPTelemetrySender(); 432 const privateAttribution = new PrivateAttributionService({ 433 dapTelemetrySender: mockSender, 434 testForceEnabled: true, 435 testDapOptions: { ohttp_relay: null }, 436 }); 437 438 const sourceHost = "source-by-ad-id.test"; 439 const targetHost = "target-by-ad-id.test"; 440 441 const adIdentifier1 = "ad_identifier_1"; 442 const adIndex1 = 1; 443 444 const adIdentifier2 = "ad_identifier_2"; 445 const adIndex2 = 2; 446 447 // Impression for adIdentifier1 at adIndex1 448 await privateAttribution.onAttributionEvent( 449 sourceHost, 450 "view", 451 adIndex1, 452 adIdentifier1, 453 targetHost 454 ); 455 456 // Impression for adIdentifier2 at adIndex2 457 await privateAttribution.onAttributionEvent( 458 sourceHost, 459 "view", 460 adIndex2, 461 adIdentifier2, 462 targetHost 463 ); 464 465 // Conversion filtering for adIdentifier1 matches impression at adIndex1 466 await privateAttribution.onAttributionConversion( 467 targetHost, 468 TASK_ID, 469 HISTOGRAM_SIZE, 470 LOOKBACK_DAYS, 471 "view", 472 [adIdentifier1], 473 [sourceHost] 474 ); 475 476 let receivedMeasurement = mockSender.receivedMeasurements.pop(); 477 Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]); 478 479 // Conversion filtering for adIdentifier2 matches impression at adIndex2 480 await privateAttribution.onAttributionConversion( 481 targetHost, 482 TASK_ID, 483 HISTOGRAM_SIZE, 484 LOOKBACK_DAYS, 485 "view", 486 [adIdentifier2], 487 [sourceHost] 488 ); 489 490 receivedMeasurement = mockSender.receivedMeasurements.pop(); 491 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 1, 0, 0]); 492 493 Assert.equal(mockSender.receivedMeasurements.length, 0); 494 }); 495 496 add_task(async function testExpiredImpressions() { 497 Services.fog.testResetFOG(); 498 499 const mockSender = new MockDAPTelemetrySender(); 500 const mockDateProvider = new MockDateProvider(); 501 const privateAttribution = new PrivateAttributionService({ 502 dapTelemetrySender: mockSender, 503 dateProvider: mockDateProvider, 504 testForceEnabled: true, 505 testDapOptions: { ohttp_relay: null }, 506 }); 507 508 const sourceHost = "source-expired.test"; 509 const targetHost = "target-expired.test"; 510 const adIdentifier = "ad_identifier_expired"; 511 const adIndex = 2; 512 513 // Register impression 514 await privateAttribution.onAttributionEvent( 515 sourceHost, 516 "view", 517 adIndex, 518 adIdentifier, 519 targetHost 520 ); 521 522 // Fast-forward time by LOOKBACK_DAYS days + 1 ms 523 mockDateProvider.add(LOOKBACK_DAYS * DAY_IN_MILLI + 1); 524 525 // Conversion doesn't match expired impression 526 await privateAttribution.onAttributionConversion( 527 targetHost, 528 TASK_ID, 529 HISTOGRAM_SIZE, 530 LOOKBACK_DAYS, 531 "view", 532 [adIdentifier], 533 [sourceHost] 534 ); 535 536 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 537 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]); 538 539 Assert.equal(mockSender.receivedMeasurements.length, 0); 540 }); 541 542 add_task(async function testConversionBudget() { 543 Services.fog.testResetFOG(); 544 545 const mockSender = new MockDAPTelemetrySender(); 546 const privateAttribution = new PrivateAttributionService({ 547 dapTelemetrySender: mockSender, 548 testForceEnabled: true, 549 testDapOptions: { ohttp_relay: null }, 550 }); 551 552 const sourceHost = "source-budget.test"; 553 const targetHost = "target-budget.test"; 554 const adIdentifier = "ad_identifier_budget"; 555 const adIndex = 3; 556 557 await privateAttribution.onAttributionEvent( 558 sourceHost, 559 "view", 560 adIndex, 561 adIdentifier, 562 targetHost 563 ); 564 565 // Measurements uploaded for conversions up to MAX_CONVERSIONS 566 for (let i = 0; i < MAX_CONVERSIONS; i++) { 567 await privateAttribution.onAttributionConversion( 568 targetHost, 569 TASK_ID, 570 HISTOGRAM_SIZE, 571 LOOKBACK_DAYS, 572 "view", 573 [adIdentifier], 574 [sourceHost] 575 ); 576 577 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 578 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]); 579 } 580 581 // Empty report uploaded on subsequent conversions 582 await privateAttribution.onAttributionConversion( 583 targetHost, 584 TASK_ID, 585 HISTOGRAM_SIZE, 586 LOOKBACK_DAYS, 587 "view", 588 [adIdentifier], 589 [sourceHost] 590 ); 591 592 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 593 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]); 594 595 Assert.equal(mockSender.receivedMeasurements.length, 0); 596 }); 597 598 add_task(async function testHistogramSize() { 599 Services.fog.testResetFOG(); 600 601 const mockSender = new MockDAPTelemetrySender(); 602 const privateAttribution = new PrivateAttributionService({ 603 dapTelemetrySender: mockSender, 604 testForceEnabled: true, 605 testDapOptions: { ohttp_relay: null }, 606 }); 607 608 const sourceHost = "source-histogram.test"; 609 const targetHost = "target-histogram.test"; 610 const adIdentifier = "ad_identifier_histogram"; 611 612 // Zero-based ad index is equal to histogram size, pushing the measurement out of the histogram's bounds 613 const adIndex = HISTOGRAM_SIZE; 614 615 await privateAttribution.onAttributionEvent( 616 sourceHost, 617 "view", 618 adIndex, 619 adIdentifier, 620 targetHost 621 ); 622 623 await privateAttribution.onAttributionConversion( 624 targetHost, 625 TASK_ID, 626 HISTOGRAM_SIZE, 627 LOOKBACK_DAYS, 628 "view", 629 [adIdentifier], 630 [sourceHost] 631 ); 632 633 const receivedMeasurement = mockSender.receivedMeasurements.pop(); 634 Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]); 635 636 Assert.equal(mockSender.receivedMeasurements.length, 0); 637 }); 638 639 add_task(async function testWithRealDAPSender() { 640 Services.fog.testResetFOG(); 641 642 // Omit mocking DAP telemetry sender in this test to defend against mock 643 // sender getting out of sync 644 const mockServer = new MockServer(); 645 mockServer.start(); 646 647 const privateAttribution = new PrivateAttributionService({ 648 testForceEnabled: true, 649 testDapOptions: { ohttp_relay: null }, 650 }); 651 652 const sourceHost = "source-telemetry.test"; 653 const targetHost = "target-telemetry.test"; 654 const adIdentifier = "ad_identifier_telemetry"; 655 const adIndex = 4; 656 657 await privateAttribution.onAttributionEvent( 658 sourceHost, 659 "view", 660 adIndex, 661 adIdentifier, 662 targetHost 663 ); 664 665 await privateAttribution.onAttributionConversion( 666 targetHost, 667 TASK_ID, 668 HISTOGRAM_SIZE, 669 LOOKBACK_DAYS, 670 "view", 671 [adIdentifier], 672 [sourceHost] 673 ); 674 675 await mockServer.stop(); 676 677 Assert.equal(mockServer.receivedReports.length, 1); 678 679 const expectedReport = { 680 contentType: "application/dap-report", 681 size: 1318, 682 }; 683 684 const receivedReport = mockServer.receivedReports.pop(); 685 Assert.deepEqual(receivedReport, expectedReport); 686 }); 687 688 function BinaryHttpResponse(status, headerNames, headerValues, content) { 689 this.status = status; 690 this.headerNames = headerNames; 691 this.headerValues = headerValues; 692 this.content = content; 693 } 694 695 BinaryHttpResponse.prototype = { 696 QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpResponse"]), 697 }; 698 699 class MockOHTTPBackend { 700 HOST; 701 #ohttpServer; 702 703 constructor() { 704 let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService( 705 Ci.nsIObliviousHttp 706 ); 707 this.#ohttpServer = ohttp.server(); 708 709 let httpServer = new HttpServer(); // This is the OHTTP relay endpoint. 710 httpServer.registerPathHandler( 711 new URL(this.getRelayAddress()).pathname, 712 this.handle_relay_request.bind(this) 713 ); 714 httpServer.start(-1); 715 this.HOST = `localhost:${httpServer.identity.primaryPort}`; 716 registerCleanupFunction(() => { 717 return new Promise(resolve => { 718 httpServer.stop(resolve); 719 }); 720 }); 721 } 722 723 getRelayAddress() { 724 return `http://${this.HOST}/relay/`; 725 } 726 727 getOHTTPConfig() { 728 return this.#ohttpServer.encodedConfig; 729 } 730 731 async handle_relay_request(request, response) { 732 let inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( 733 Ci.nsIBinaryInputStream 734 ); 735 inputStream.setInputStream(request.bodyInputStream); 736 let requestBody = inputStream.readByteArray(inputStream.available()); 737 let ohttpRequest = this.#ohttpServer.decapsulate(requestBody); 738 let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService( 739 Ci.nsIBinaryHttp 740 ); 741 let decodedRequest = bhttp.decodeRequest(ohttpRequest.request); 742 response.processAsync(); 743 let real_destination = 744 decodedRequest.scheme + 745 "://" + 746 decodedRequest.authority + 747 decodedRequest.path; 748 let innerBody = new Uint8Array(decodedRequest.content); 749 let innerRequestHeaders = Object.fromEntries( 750 decodedRequest.headerNames.map((name, index) => { 751 return [name, decodedRequest.headerValues[index]]; 752 }) 753 ); 754 let innerResponse = await fetch(real_destination, { 755 method: decodedRequest.method, 756 headers: innerRequestHeaders, 757 body: innerBody, 758 }); 759 let bytes = new Uint8Array(await innerResponse.arrayBuffer()); 760 let binaryResponse = new BinaryHttpResponse( 761 innerResponse.status, 762 ["Content-Type"], 763 ["application/octet-stream"], 764 bytes 765 ); 766 let encResponse = ohttpRequest.encapsulate( 767 bhttp.encodeResponse(binaryResponse) 768 ); 769 response.setStatusLine(request.httpVersion, 200, "OK"); 770 response.setHeader("Content-Type", "message/ohttp-res", false); 771 772 let bstream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance( 773 Ci.nsIBinaryOutputStream 774 ); 775 bstream.setOutputStream(response.bodyOutputStream); 776 bstream.writeByteArray(encResponse); 777 response.finish(); 778 } 779 } 780 781 add_task(async function testWithRealDAPSenderAndOHTTP() { 782 Services.fog.testResetFOG(); 783 784 const mockServer = new MockServer(); 785 mockServer.start(); 786 787 let ohttpBackend = new MockOHTTPBackend(); 788 789 let testDapOptions = { 790 ohttp_relay: ohttpBackend.getRelayAddress(), 791 ohttp_hpke: ohttpBackend.getOHTTPConfig(), 792 }; 793 794 const privateAttribution = new PrivateAttributionService({ 795 testForceEnabled: true, 796 testDapOptions, 797 }); 798 799 const sourceHost = "source-telemetry.test"; 800 const targetHost = "target-telemetry.test"; 801 const adIdentifier = "ad_identifier_telemetry"; 802 const adIndex = 4; 803 804 await privateAttribution.onAttributionEvent( 805 sourceHost, 806 "view", 807 adIndex, 808 adIdentifier, 809 targetHost 810 ); 811 812 await privateAttribution.onAttributionConversion( 813 targetHost, 814 TASK_ID, 815 HISTOGRAM_SIZE, 816 LOOKBACK_DAYS, 817 "view", 818 [adIdentifier], 819 [sourceHost] 820 ); 821 822 await mockServer.stop(); 823 824 Assert.equal(mockServer.receivedReports.length, 1); 825 826 const expectedReport = { 827 contentType: "application/dap-report", 828 size: 1318, 829 }; 830 831 const receivedReport = mockServer.receivedReports.pop(); 832 Assert.deepEqual(receivedReport, expectedReport); 833 });