tor-browser

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

interest-group-update.https.window.js (15387B)


      1 // META: script=/resources/testdriver.js
      2 // META: script=/resources/testdriver-vendor.js
      3 // META: script=/common/utils.js
      4 // META: script=resources/fledge-util.sub.js
      5 // META: script=/common/subset-tests.js
      6 // META: timeout=long
      7 // META: variant=?1-4
      8 // META: variant=?5-9
      9 // META: variant=?10-14
     10 // META: variant=?15-19
     11 // META: variant=?20-last
     12 
     13 "use strict";
     14 
     15 // This test repeatedly runs auctions to verify an update. A modified bidding script
     16 // continuously throws errors until it detects the expected change in the interest group
     17 // field. This update then stops the auction cycle.
     18 const makeTestForUpdate = ({
     19  // Test name
     20  name,
     21  // fieldname that is getting updated
     22  interestGroupFieldName,
     23  // This is used to check if update has happened.
     24  expectedValue,
     25  // This is used to create the update response, by default it will always send
     26  // back the `expectedValue`. Extra steps to make a deep copy.
     27  responseOverride = expectedValue,
     28  // Overrides to the interest group.
     29  interestGroupOverrides = {},
     30  // Overrides to the auction config.
     31  auctionConfigOverrides = {},
     32 }) => {
     33  subsetTest(promise_test, async test => {
     34    const uuid = generateUuid(test);
     35    let extraBiddingLogic = ``;
     36 
     37    let replacePlaceholders = (ads) => ads.forEach(element => {
     38      element.renderURL = element.renderURL.replace(`UUID-PLACEHOLDER`, uuid);
     39    });
     40 
     41    // Testing 'ads' requires some additional setup due to it's reliance
     42    // on createRenderURL, specifically the bidding script used checks to make
     43    // sure the `uuid` is the correct one for the test. We use a renderURL
     44    // with a placeholder 'UUID-PLACEHOLDER' and make sure to replace it
     45    // before moving on to the test.
     46    if (interestGroupFieldName === `ads`) {
     47      if (interestGroupFieldName in interestGroupOverrides) {
     48        replacePlaceholders(interestGroupOverrides[interestGroupFieldName]);
     49      }
     50      replacePlaceholders(responseOverride);
     51      replacePlaceholders(expectedValue);
     52    }
     53    // When checking the render URL, both the deprecated 'renderUrl' and the updated 'renderURL' might exist
     54    // in the interest group simultaneously, so this test deletes the 'renderUrl' to ensure a
     55    // clean comparison with deepEquals.
     56    if (interestGroupFieldName === `ads` || interestGroupFieldName === `adComponents`) {
     57      extraBiddingLogic = `
     58      interestGroup.${interestGroupFieldName}.forEach(element => {
     59        delete element.renderUrl;
     60      });`
     61    }
     62 
     63    let expectedValueJSON = JSON.stringify(expectedValue);
     64    // When the update has not yet been seen, throw an error which will cause the
     65    // auction not to have a result.
     66    interestGroupOverrides.biddingLogicURL = createBiddingScriptURL({
     67      generateBid: `
     68      ${extraBiddingLogic}
     69      if (!deepEquals(interestGroup.${interestGroupFieldName}, ${expectedValueJSON})) {
     70        throw '${interestGroupFieldName} is ' +
     71            JSON.stringify(interestGroup.${interestGroupFieldName}) +
     72            ' instead of ' + '${expectedValueJSON}';
     73      }`
     74    });
     75 
     76    let responseBody = {};
     77    responseBody[interestGroupFieldName] = responseOverride;
     78    let updateParams = {
     79      body: JSON.stringify(responseBody),
     80      uuid: uuid
     81    };
     82    interestGroupOverrides.updateURL = createUpdateURL(updateParams);
     83    await joinInterestGroup(test, uuid, interestGroupOverrides);
     84    if (interestGroupFieldName === `ads`) {
     85      let interestGroup = createInterestGroupForOrigin(
     86          uuid, window.location.origin, interestGroupOverrides);
     87      interestGroup.ads = responseOverride;
     88      await makeInterestGroupKAnonymous(interestGroup);
     89    }
     90 
     91    // Run an auction until there's a winner, which means update occurred.
     92    let auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
     93    expectNoWinner(auctionResult);
     94    while (!auctionResult) {
     95      auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
     96    }
     97  }, name);
     98 };
     99 
    100 // In order to test the update process does not update certain fields, this test uses two interest groups:
    101 
    102 // * `failedUpdateGroup`: Receives an invalid update, and will continue to throw errors until the update
    103 //                        occurs (which shouldn't happen). This group will have a high bid to ensure if
    104 //                        there was ever a tie, it would win.
    105 // * `successUpdateGroup`: A hard-coded interest group that receives a update and will signal the change
    106 //                         by throwing an error.
    107 
    108 // By tracking render URLs, this test guarantees that only the URL associated with the correct update
    109 // (`goodUpdateRenderURL`) is used, and the incorrect URL (`badUpdateRenderURL`) isn't. The test runs
    110 // auctions repeatedly until the update in `successUpdateGroup` stops an auction from producing a winner.
    111 // It then will run one final auction. If there's still no winner, it can infer that `failedUpdateGroup`
    112 // would have received the update if it were propagating correctly.
    113 
    114 // If there was a bug in the implementation, a possible case can occur and manifest as a flaky test.
    115 // In this scenerio with the current structure of the Protected Audience API, the `successUpdateGroup`
    116 // updates, and so does the `failedUpdateGroup`, but the `failedUpdateGroup` update happens significantly
    117 // after the  `successUpdateGroup`'s update. In an effort to combat this, after the while loop we run
    118 // another auction to ensure there is no winner (both cases should throw), but depending how slow the
    119 // update takes, this flaky issue still can **possibly** occur.
    120 const makeTestForNoUpdate = ({
    121  // Test name
    122  name,
    123  // fieldname that is should not be getting updated
    124  interestGroupFieldName,
    125  // this is used to create the update response and check if it did not happen.
    126  responseOverride,
    127  // Overrides to the auction config.
    128  auctionConfigOverrides = {},
    129  // Overrides to the interest group.
    130  failedUpdateGroup = {},
    131 }) => {
    132  subsetTest(promise_test, async test => {
    133    const uuid = generateUuid(test);
    134    // successUpdateGroup
    135 
    136    // These are used in `successUpdateGroup` in order to get a proper update.
    137    let successUpdateGroup = {};
    138    let successUpdateField = `userBiddingSignals`;
    139    let successUpdateFieldExpectedValue = { 'test': 20 };
    140 
    141    const goodUpdateRenderURL = createTrackerURL(window.location.origin, uuid, 'track_get', 'good_update');
    142    successUpdateGroup.ads = [{ 'renderURL': goodUpdateRenderURL }];
    143    successUpdateGroup.biddingLogicURL = createBiddingScriptURL({
    144      generateBid: `
    145      if (deepEquals(interestGroup.${successUpdateField}, ${JSON.stringify(successUpdateFieldExpectedValue)})){
    146        throw '${successUpdateField} has updated and is ' +
    147            '${JSON.stringify(successUpdateFieldExpectedValue)}.'
    148      }`,
    149      bid: 5
    150    });
    151 
    152    let successResponseBody = {};
    153    successResponseBody[successUpdateField] = successUpdateFieldExpectedValue;
    154    let successUpdateParams = {
    155      body: JSON.stringify(successResponseBody),
    156      uuid: uuid
    157    };
    158    successUpdateGroup.updateURL = createUpdateURL(successUpdateParams);
    159    await joinInterestGroup(test, uuid, successUpdateGroup);
    160    ///////////////////////// successUpdateGroup
    161 
    162    // failedUpdateGroup
    163    const badUpdateRenderURL = createTrackerURL(window.location.origin, uuid, `track_get`, `bad_update`);
    164    // Name needed so we don't have two IGs with same name.
    165    failedUpdateGroup.name = failedUpdateGroup.name ? failedUpdateGroup.name : `IG name`
    166    failedUpdateGroup.ads = [{ 'renderURL': badUpdateRenderURL }];
    167    failedUpdateGroup.biddingLogicURL = createBiddingScriptURL({
    168      generateBid: `
    169      if (!deepEquals(interestGroup.${interestGroupFieldName}, ${JSON.stringify(responseOverride)})){
    170            throw '${interestGroupFieldName} is as expected: '+
    171            JSON.stringify(interestGroup.${interestGroupFieldName});
    172      }`,
    173      bid: 1000
    174    });
    175    let failedResponseBody = {};
    176    failedResponseBody[interestGroupFieldName] = responseOverride;
    177 
    178    let failedUpdateParams = {
    179      body: JSON.stringify(failedResponseBody),
    180      uuid: uuid
    181    };
    182 
    183    failedUpdateGroup.updateURL = createUpdateURL(failedUpdateParams);
    184    await joinInterestGroup(test, uuid, failedUpdateGroup);
    185    ///////////////////////// failedUpdateGroup
    186 
    187    // First result should be not be null, `successUpdateGroup` throws when update is detected so until then,
    188    // run and observe the requests to ensure only `goodUpdateRenderURL` is fetched.
    189    let auctionResult = await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
    190    while (auctionResult) {
    191      createAndNavigateFencedFrame(test, auctionResult);
    192      await waitForObservedRequests(
    193        uuid,
    194        [goodUpdateRenderURL, createSellerReportURL(uuid)]);
    195      await fetch(createCleanupURL(uuid));
    196      auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
    197    }
    198    // Re-run to ensure null because:
    199    // `successUpdateGroup` should be throwing since update occurred.
    200    // `failedUpdateGroup` should be throwing since update did not occur.
    201    await runBasicFledgeTestExpectingNoWinner(test, uuid, auctionConfigOverrides);
    202  }, name);
    203 };
    204 
    205 // Helper to eliminate rewriting a long call to createRenderURL().
    206 // Only thing to change would be signalParams to differentiate between URLs.
    207 const createTempRenderURL = (signalsParams = null) => {
    208  return createRenderURL(/*uuid=*/`UUID-PLACEHOLDER`,/*script=*/ null,/*signalParams=*/ signalsParams,/*origin=*/ null);
    209 };
    210 
    211 makeTestForUpdate({
    212  name: 'userBiddingSignals update overwrites everything in the field.',
    213  interestGroupFieldName: 'userBiddingSignals',
    214  expectedValue: { 'test': 20 },
    215  interestGroupOverrides: {
    216    userBiddingSignals: { 'test': 10, 'extra_value': true },
    217  }
    218 });
    219 
    220 makeTestForUpdate({
    221  name: 'userBiddingSignals updated multi-type',
    222  interestGroupFieldName: 'userBiddingSignals',
    223  expectedValue: { 'test': 20, 5: [1, [false, false, true], 3, 'Hello'] },
    224  interestGroupOverrides: {
    225    userBiddingSignals: { 'test': 10 },
    226  }
    227 });
    228 
    229 makeTestForUpdate({
    230  name: 'userBiddingSignals updated to non object',
    231  interestGroupFieldName: 'userBiddingSignals',
    232  expectedValue: 5,
    233  interestGroupOverrides: {
    234    userBiddingSignals: { 'test': 10 },
    235  }
    236 });
    237 
    238 makeTestForUpdate({
    239  name: 'userBiddingSignals updated to null',
    240  interestGroupFieldName: 'userBiddingSignals',
    241  expectedValue: null,
    242  interestGroupOverrides: {
    243    userBiddingSignals: { 'test': 10 },
    244  }
    245 });
    246 
    247 makeTestForUpdate({
    248  name: 'trustedBiddingSignalsKeys updated correctly',
    249  interestGroupFieldName: 'trustedBiddingSignalsKeys',
    250  expectedValue: ['new_key', 'old_key'],
    251  interestGroupOverrides: {
    252    trustedBiddingSignalsKeys: ['old_key'],
    253  }
    254 });
    255 
    256 makeTestForUpdate({
    257  name: 'trustedBiddingSignalsKeys updated to empty array.',
    258  interestGroupFieldName: 'trustedBiddingSignalsKeys',
    259  expectedValue: [],
    260  interestGroupOverrides: {
    261    trustedBiddingSignalsKeys: ['old_key'],
    262  }
    263 });
    264 
    265 
    266 makeTestForUpdate({
    267  name: 'trustedBiddingSignalsSlotSizeMode updated to slot-size',
    268  interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
    269  expectedValue: 'slot-size',
    270  interestGroupOverrides: {
    271    trustedBiddingSignalsKeys: ['key'],
    272    trustedBiddingSignalsSlotSizeMode: 'none',
    273  }
    274 });
    275 
    276 makeTestForUpdate({
    277  name: 'trustedBiddingSignalsSlotSizeMode updated to all-slots-requested-sizes',
    278  interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
    279  expectedValue: 'all-slots-requested-sizes',
    280  interestGroupOverrides: {
    281    trustedBiddingSignalsKeys: ['key'],
    282    trustedBiddingSignalsSlotSizeMode: 'slot-size',
    283  }
    284 });
    285 
    286 makeTestForUpdate({
    287  name: 'trustedBiddingSignalsSlotSizeMode updated to none',
    288  interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
    289  expectedValue: 'none',
    290  interestGroupOverrides: {
    291    trustedBiddingSignalsKeys: ['key'],
    292    trustedBiddingSignalsSlotSizeMode: 'slot-size',
    293  }
    294 });
    295 
    296 makeTestForUpdate({
    297  name: 'trustedBiddingSignalsSlotSizeMode updated to unknown, defaults to none',
    298  interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
    299  expectedValue: 'none',
    300  responseOverride: 'unknown-type',
    301  interestGroupOverrides: {
    302    trustedBiddingSignalsKeys: ['key'],
    303    trustedBiddingSignalsSlotSizeMode: 'slot-size',
    304  }
    305 });
    306 
    307 makeTestForUpdate({
    308  name: 'ads updated from 2 ads to 1.',
    309  interestGroupFieldName: 'ads',
    310  expectedValue: [
    311    { renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
    312  ],
    313  interestGroupOverrides: {
    314    ads: [{ renderURL: createTempRenderURL() },
    315    { renderURL: createTempRenderURL() }]
    316  }
    317 });
    318 
    319 makeTestForUpdate({
    320  name: 'ads updated from 1 ad to 2.',
    321  interestGroupFieldName: 'ads',
    322  expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
    323                  { renderURL: createTempRenderURL('new_url2'), metadata: 'test2-new' }],
    324  interestGroupOverrides: {
    325    ads: [{ renderURL: createTempRenderURL() }]
    326  }
    327 });
    328 
    329 makeTestForUpdate({
    330  name: 'adComponents updated from 1 adComponent to 2.',
    331  interestGroupFieldName: 'adComponents',
    332  expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
    333                  { renderURL: createTempRenderURL('new_url2'), metadata: 'test2' }],
    334  interestGroupOverrides: {
    335    adComponents: [{ renderURL: createTempRenderURL(), metadata: 'test1' }]
    336  },
    337 });
    338 
    339 makeTestForUpdate({
    340  name: 'adComponents updated from 2 adComponents to 1.',
    341  interestGroupFieldName: 'adComponents',
    342  expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' }],
    343  interestGroupOverrides: {
    344    adComponents: [{ renderURL: createTempRenderURL() },
    345    { renderURL: createTempRenderURL() }]
    346  },
    347 });
    348 
    349 makeTestForUpdate({
    350  name: 'executionMode updated to frozen context',
    351  interestGroupFieldName: 'executionMode',
    352  expectedValue: 'frozen-context',
    353  interestGroupOverrides: {
    354    executionMode: 'compatibility',
    355  }
    356 });
    357 
    358 makeTestForUpdate({
    359  name: 'executionMode updated to compatibility',
    360  interestGroupFieldName: 'executionMode',
    361  expectedValue: 'compatibility',
    362  interestGroupOverrides: {
    363    executionMode: 'frozen-context',
    364  }
    365 });
    366 
    367 makeTestForUpdate({
    368  name: 'executionMode updated to group by origin',
    369  interestGroupFieldName: 'executionMode',
    370  expectedValue: 'group-by-origin',
    371  interestGroupOverrides: {
    372    executionMode: 'compatibility',
    373  }
    374 });
    375 
    376 makeTestForNoUpdate({
    377  name: 'executionMode updated with invalid input',
    378  interestGroupFieldName: 'executionMode',
    379  responseOverride: 'unknown-type',
    380 });
    381 
    382 makeTestForNoUpdate({
    383  name: 'owner cannot be updated.',
    384  interestGroupFieldName: 'owner',
    385  responseOverride: OTHER_ORIGIN1,
    386  auctionConfigOverrides: {
    387    interestGroupBuyers: [OTHER_ORIGIN1, window.location.origin]
    388  }
    389 });
    390 
    391 makeTestForNoUpdate({
    392  name: 'name cannot be updated.',
    393  interestGroupFieldName: 'name',
    394  responseOverride: 'new_name',
    395  failedUpdateGroup: { name: 'name2' },
    396 });
    397 
    398 makeTestForNoUpdate({
    399  name: 'executionMode not updated when unknown type.',
    400  interestGroupFieldName: 'executionMode',
    401  responseOverride: 'unknown-type',
    402  failedUpdateGroup: { executionMode: 'compatibility' },
    403 });
    404 
    405 makeTestForNoUpdate({
    406  name: 'trustedBiddingSignalsKeys not updated when bad value.',
    407  interestGroupFieldName: 'trustedBiddingSignalsKeys',
    408  responseOverride: 5,
    409  failedUpdateGroup: {
    410    trustedBiddingSignalsKeys: ['key'],
    411  },
    412 });