summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js')
-rw-r--r--testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js406
1 files changed, 406 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js b/testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js
new file mode 100644
index 0000000000..59b3736b09
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/interest-group-update.https.window.js
@@ -0,0 +1,406 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.sub.js
+// META: script=/common/subset-tests.js
+// META: timeout=long
+// META: variant=?1-4
+// META: variant=?5-9
+// META: variant=?10-14
+// META: variant=?15-19
+// META: variant=?20-last
+
+"use strict;"
+
+// This test repeatedly runs auctions to verify an update. A modified bidding script
+// continuously throws errors until it detects the expected change in the interest group
+// field. This update then stops the auction cycle.
+const makeTestForUpdate = ({
+ // Test name
+ name,
+ // fieldname that is getting updated
+ interestGroupFieldName,
+ // This is used to check if update has happened.
+ expectedValue,
+ // This is used to create the update response, by default it will always send
+ // back the `expectedValue`. Extra steps to make a deep copy.
+ responseOverride = expectedValue,
+ // Overrides to the interest group.
+ interestGroupOverrides = {},
+ // Overrides to the auction config.
+ auctionConfigOverrides = {},
+}) => {
+ subsetTest(promise_test, async test => {
+ const uuid = generateUuid(test);
+ extraBiddingLogic = ``;
+
+ let replacePlaceholders = (ads) => ads.forEach(element => {
+ element.renderURL = element.renderURL.replace(`UUID-PLACEHOLDER`, uuid);
+ });
+
+ // Testing 'ads' requires some additional setup due to it's reliance
+ // on createRenderURL, specifically the bidding script used checks to make
+ // sure the `uuid` is the correct one for the test. We use a renderURL
+ // with a placeholder 'UUID-PLACEHOLDER' and make sure to replace it
+ // before moving on to the test.
+ if (interestGroupFieldName === `ads`) {
+ if (interestGroupFieldName in interestGroupOverrides) {
+ replacePlaceholders(interestGroupOverrides[interestGroupFieldName]);
+ }
+ replacePlaceholders(responseOverride);
+ replacePlaceholders(expectedValue);
+ }
+ // When checking the render URL, both the deprecated 'renderUrl' and the updated 'renderURL' might exist
+ // in the interest group simultaneously, so this test deletes the 'renderUrl' to ensure a
+ // clean comparison with deepEquals.
+ if (interestGroupFieldName === `ads` || interestGroupFieldName === `adComponents`) {
+ extraBiddingLogic = `
+ interestGroup.${interestGroupFieldName}.forEach(element => {
+ delete element.renderUrl;
+ });`
+ }
+
+ let expectedValueJSON = JSON.stringify(expectedValue);
+ // When the update has not yet been seen, throw an error which will cause the
+ // auction not to have a result.
+ interestGroupOverrides.biddingLogicURL = createBiddingScriptURL({
+ generateBid: `
+ ${extraBiddingLogic}
+ if (!deepEquals(interestGroup.${interestGroupFieldName}, ${expectedValueJSON})) {
+ throw '${interestGroupFieldName} is ' +
+ JSON.stringify(interestGroup.${interestGroupFieldName}) +
+ ' instead of ' + '${expectedValueJSON}';
+ }`
+ });
+
+ let responseBody = {};
+ responseBody[interestGroupFieldName] = responseOverride;
+ let updateParams = {
+ body: JSON.stringify(responseBody),
+ uuid: uuid
+ };
+ interestGroupOverrides.updateURL = createUpdateURL(updateParams);
+ await joinInterestGroup(test, uuid, interestGroupOverrides);
+
+ // Run an auction until there's a winner, which means update occurred.
+ let auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
+ expectNoWinner(auctionResult);
+ while (!auctionResult) {
+ auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
+ }
+ }, name);
+};
+
+// In order to test the update process does not update certain fields, this test uses two interest groups:
+
+// * `failedUpdateGroup`: Receives an invalid update, and will continue to throw errors until the update
+// occurs (which shouldn't happen). This group will have a high bid to ensure if
+// there was ever a tie, it would win.
+// * `successUpdateGroup`: A hard-coded interest group that receives a update and will signal the change
+// by throwing an error.
+
+// By tracking render URLs, this test guarantees that only the URL associated with the correct update
+// (`goodUpdateRenderURL`) is used, and the incorrect URL (`badUpdateRenderURL`) isn't. The test runs
+// auctions repeatedly until the update in `successUpdateGroup` stops an auction from producing a winner.
+// It then will run one final auction. If there's still no winner, it can infer that `failedUpdateGroup`
+// would have received the update if it were propagating correctly.
+
+// If there was a bug in the implementation, a possible case can occur and manifest as a flaky test.
+// In this scenerio with the current structure of the Protected Audience API, the `successUpdateGroup`
+// updates, and so does the `failedUpdateGroup`, but the `failedUpdateGroup` update happens significantly
+// after the `successUpdateGroup`'s update. In an effort to combat this, after the while loop we run
+// another auction to ensure there is no winner (both cases should throw), but depending how slow the
+// update takes, this flaky issue still can **possibly** occur.
+const makeTestForNoUpdate = ({
+ // Test name
+ name,
+ // fieldname that is should not be getting updated
+ interestGroupFieldName,
+ // this is used to create the update response and check if it did not happen.
+ responseOverride,
+ // Overrides to the auction config.
+ auctionConfigOverrides = {},
+ // Overrides to the interest group.
+ failedUpdateGroup = {},
+}) => {
+ subsetTest(promise_test, async test => {
+ const uuid = generateUuid(test);
+ // successUpdateGroup
+
+ // These are used in `successUpdateGroup` in order to get a proper update.
+ let successUpdateGroup = {};
+ let successUpdateField = `userBiddingSignals`;
+ let successUpdateFieldExpectedValue = { 'test': 20 };
+
+ const goodUpdateRenderURL = createTrackerURL(window.location.origin, uuid, 'track_get', 'good_update');
+ successUpdateGroup.ads = [{ 'renderURL': goodUpdateRenderURL }];
+ successUpdateGroup.biddingLogicURL = createBiddingScriptURL({
+ generateBid: `
+ if (deepEquals(interestGroup.${successUpdateField}, ${JSON.stringify(successUpdateFieldExpectedValue)})){
+ throw '${successUpdateField} has updated and is ' +
+ '${JSON.stringify(successUpdateFieldExpectedValue)}.'
+ }`,
+ bid: 5
+ });
+
+ let successResponseBody = {};
+ successResponseBody[successUpdateField] = successUpdateFieldExpectedValue;
+ let successUpdateParams = {
+ body: JSON.stringify(successResponseBody),
+ uuid: uuid
+ };
+ successUpdateGroup.updateURL = createUpdateURL(successUpdateParams);
+ await joinInterestGroup(test, uuid, successUpdateGroup);
+ ///////////////////////// successUpdateGroup
+
+ // failedUpdateGroup
+ const badUpdateRenderURL = createTrackerURL(window.location.origin, uuid, `track_get`, `bad_update`);
+ // Name needed so we don't have two IGs with same name.
+ failedUpdateGroup.name = failedUpdateGroup.name ? failedUpdateGroup.name : `IG name`
+ failedUpdateGroup.ads = [{ 'renderURL': badUpdateRenderURL }];
+ failedUpdateGroup.biddingLogicURL = createBiddingScriptURL({
+ generateBid: `
+ if (!deepEquals(interestGroup.${interestGroupFieldName}, ${JSON.stringify(responseOverride)})){
+ throw '${interestGroupFieldName} is as expected: '+
+ JSON.stringify(interestGroup.${interestGroupFieldName});
+ }`,
+ bid: 1000
+ });
+ let failedResponseBody = {};
+ failedResponseBody[interestGroupFieldName] = responseOverride;
+
+ let failedUpdateParams = {
+ body: JSON.stringify(failedResponseBody),
+ uuid: uuid
+ };
+
+ failedUpdateGroup.updateURL = createUpdateURL(failedUpdateParams);
+ await joinInterestGroup(test, uuid, failedUpdateGroup);
+ ///////////////////////// failedUpdateGroup
+
+ // First result should be not be null, `successUpdateGroup` throws when update is detected so until then,
+ // run and observe the requests to ensure only `goodUpdateRenderURL` is fetched.
+ let auctionResult = await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
+ while (auctionResult) {
+ createAndNavigateFencedFrame(test, auctionResult);
+ await waitForObservedRequests(
+ uuid,
+ [goodUpdateRenderURL, createSellerReportURL(uuid)]);
+ await fetch(createCleanupURL(uuid));
+ auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
+ }
+ // Re-run to ensure null because:
+ // `successUpdateGroup` should be throwing since update occurred.
+ // `failedUpdateGroup` should be throwing since update did not occur.
+ await runBasicFledgeTestExpectingNoWinner(test, uuid, auctionConfigOverrides);
+ }, name);
+};
+
+// Helper to eliminate rewriting a long call to createRenderURL().
+// Only thing to change would be signalParams to differentiate between URLs.
+const createTempRenderURL = (signalsParams = null) => {
+ return createRenderURL(/*uuid=*/`UUID-PLACEHOLDER`,/*script=*/ null,/*signalParams=*/ signalsParams,/*origin=*/ null);
+};
+
+makeTestForUpdate({
+ name: 'userBiddingSignals update overwrites everything in the field.',
+ interestGroupFieldName: 'userBiddingSignals',
+ expectedValue: { 'test': 20 },
+ interestGroupOverrides: {
+ userBiddingSignals: { 'test': 10, 'extra_value': true },
+ }
+});
+
+makeTestForUpdate({
+ name: 'userBiddingSignals updated multi-type',
+ interestGroupFieldName: 'userBiddingSignals',
+ expectedValue: { 'test': 20, 5: [1, [false, false, true], 3, 'Hello'] },
+ interestGroupOverrides: {
+ userBiddingSignals: { 'test': 10 },
+ }
+});
+
+makeTestForUpdate({
+ name: 'userBiddingSignals updated to non object',
+ interestGroupFieldName: 'userBiddingSignals',
+ expectedValue: 5,
+ interestGroupOverrides: {
+ userBiddingSignals: { 'test': 10 },
+ }
+});
+
+makeTestForUpdate({
+ name: 'userBiddingSignals updated to null',
+ interestGroupFieldName: 'userBiddingSignals',
+ expectedValue: null,
+ interestGroupOverrides: {
+ userBiddingSignals: { 'test': 10 },
+ }
+});
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsKeys updated correctly',
+ interestGroupFieldName: 'trustedBiddingSignalsKeys',
+ expectedValue: ['new_key', 'old_key'],
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['old_key'],
+ }
+});
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsKeys updated to empty array.',
+ interestGroupFieldName: 'trustedBiddingSignalsKeys',
+ expectedValue: [],
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['old_key'],
+ }
+});
+
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsSlotSizeMode updated to slot-size',
+ interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
+ expectedValue: 'slot-size',
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['key'],
+ trustedBiddingSignalsSlotSizeMode: 'none',
+ }
+});
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsSlotSizeMode updated to all-slots-requested-sizes',
+ interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
+ expectedValue: 'all-slots-requested-sizes',
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['key'],
+ trustedBiddingSignalsSlotSizeMode: 'slot-size',
+ }
+});
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsSlotSizeMode updated to none',
+ interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
+ expectedValue: 'none',
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['key'],
+ trustedBiddingSignalsSlotSizeMode: 'slot-size',
+ }
+});
+
+makeTestForUpdate({
+ name: 'trustedBiddingSignalsSlotSizeMode updated to unknown, defaults to none',
+ interestGroupFieldName: 'trustedBiddingSignalsSlotSizeMode',
+ expectedValue: 'none',
+ responseOverride: 'unknown-type',
+ interestGroupOverrides: {
+ trustedBiddingSignalsKeys: ['key'],
+ trustedBiddingSignalsSlotSizeMode: 'slot-size',
+ }
+});
+
+makeTestForUpdate({
+ name: 'ads updated from 2 ads to 1.',
+ interestGroupFieldName: 'ads',
+ expectedValue: [
+ { renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
+ ],
+ interestGroupOverrides: {
+ ads: [{ renderURL: createTempRenderURL() },
+ { renderURL: createTempRenderURL() }]
+ }
+});
+
+makeTestForUpdate({
+ name: 'ads updated from 1 ad to 2.',
+ interestGroupFieldName: 'ads',
+ expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
+ { renderURL: createTempRenderURL('new_url2'), metadata: 'test2-new' }],
+ interestGroupOverrides: {
+ ads: [{ renderURL: createTempRenderURL() }]
+ }
+});
+
+makeTestForUpdate({
+ name: 'adComponents updated from 1 adComponent to 2.',
+ interestGroupFieldName: 'adComponents',
+ expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' },
+ { renderURL: createTempRenderURL('new_url2'), metadata: 'test2' }],
+ interestGroupOverrides: {
+ adComponents: [{ renderURL: createTempRenderURL(), metadata: 'test1' }]
+ },
+});
+
+makeTestForUpdate({
+ name: 'adComponents updated from 2 adComponents to 1.',
+ interestGroupFieldName: 'adComponents',
+ expectedValue: [{ renderURL: createTempRenderURL('new_url1'), metadata: 'test1-new' }],
+ interestGroupOverrides: {
+ adComponents: [{ renderURL: createTempRenderURL() },
+ { renderURL: createTempRenderURL() }]
+ },
+});
+
+makeTestForUpdate({
+ name: 'executionMode updated to frozen context',
+ interestGroupFieldName: 'executionMode',
+ expectedValue: 'frozen-context',
+ interestGroupOverrides: {
+ executionMode: 'compatibility',
+ }
+});
+
+makeTestForUpdate({
+ name: 'executionMode updated to compatibility',
+ interestGroupFieldName: 'executionMode',
+ expectedValue: 'compatibility',
+ interestGroupOverrides: {
+ executionMode: 'frozen-context',
+ }
+});
+
+makeTestForUpdate({
+ name: 'executionMode updated to group by origin',
+ interestGroupFieldName: 'executionMode',
+ expectedValue: 'group-by-origin',
+ interestGroupOverrides: {
+ executionMode: 'compatibility',
+ }
+});
+
+makeTestForNoUpdate({
+ name: 'executionMode updated with invalid input',
+ interestGroupFieldName: 'executionMode',
+ responseOverride: 'unknown-type',
+});
+
+makeTestForNoUpdate({
+ name: 'owner cannot be updated.',
+ interestGroupFieldName: 'owner',
+ responseOverride: OTHER_ORIGIN1,
+ auctionConfigOverrides: {
+ interestGroupBuyers: [OTHER_ORIGIN1, window.location.origin]
+ }
+});
+
+makeTestForNoUpdate({
+ name: 'name cannot be updated.',
+ interestGroupFieldName: 'name',
+ responseOverride: 'new_name',
+ failedUpdateGroup: { name: 'name2' },
+});
+
+makeTestForNoUpdate({
+ name: 'executionMode not updated when unknown type.',
+ interestGroupFieldName: 'executionMode',
+ responseOverride: 'unknown-type',
+ failedUpdateGroup: { executionMode: 'compatibility' },
+});
+
+makeTestForNoUpdate({
+ name: 'trustedBiddingSignalsKeys not updated when bad value.',
+ interestGroupFieldName: 'trustedBiddingSignalsKeys',
+ responseOverride: 5,
+ failedUpdateGroup: {
+ trustedBiddingSignalsKeys: ['key'],
+ },
+});
+