412 lines
15 KiB
JavaScript
412 lines
15 KiB
JavaScript
// META: script=/resources/testdriver.js
|
|
// META: script=/resources/testdriver-vendor.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);
|
|
let 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);
|
|
if (interestGroupFieldName === `ads`) {
|
|
let interestGroup = createInterestGroupForOrigin(
|
|
uuid, window.location.origin, interestGroupOverrides);
|
|
interestGroup.ads = responseOverride;
|
|
await makeInterestGroupKAnonymous(interestGroup);
|
|
}
|
|
|
|
// 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'],
|
|
},
|
|
});
|