summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fledge/tentative
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fledge/tentative')
-rw-r--r--testing/web-platform/tests/fledge/tentative/TODO48
-rw-r--r--testing/web-platform/tests/fledge/tentative/auction-config.https.sub.window.js214
-rw-r--r--testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers-insecure-context.tentative.http.html11
-rw-r--r--testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers.tentative.https.html12
-rw-r--r--testing/web-platform/tests/fledge/tentative/insecure-context.window.js8
-rw-r--r--testing/web-platform/tests/fledge/tentative/join-leave-ad-interest-group.https.sub.window.js570
-rw-r--r--testing/web-platform/tests/fledge/tentative/no-winner.https.sub.window.js80
-rw-r--r--testing/web-platform/tests/fledge/tentative/register-ad-beacon.https.sub.window.js323
-rw-r--r--testing/web-platform/tests/fledge/tentative/reporting-arguments.https.sub.window.js291
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py57
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py54
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/empty.html1
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/fenced-frame.sub.py19
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/fledge-util.js319
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/request-tracker.py97
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py124
-rw-r--r--testing/web-platform/tests/fledge/tentative/send-report-to.https.sub.window.js163
-rw-r--r--testing/web-platform/tests/fledge/tentative/trusted-bidding-signals.https.sub.window.js431
18 files changed, 2822 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fledge/tentative/TODO b/testing/web-platform/tests/fledge/tentative/TODO
new file mode 100644
index 0000000000..d50e492611
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/TODO
@@ -0,0 +1,48 @@
+Need tests for (likely not a complete list):
+
+* directFromSellerSignals.
+* All generateBid() and scoreAd() input parameters.
+* All interest group fields (passed to auction, have effect on auction).
+ Validation when joining/leaving interest group is already covered.
+* Filtering/prioritization (including bidding signals influencing priorities)
+* Size restrictions.
+* Updates (both after auction and triggered).
+* All auctionConfig parameters (including invalid auctionConfigs, and ones
+ with no buyers).
+* Interest group expiration.
+* Multiple buyers.
+* Multiple interest group with same owner.
+* Multiple origin auctions (buyer != publisher != seller).
+* Multiple frame tests (including join IG policy, run auction policy,
+ loading URNs in fencedframes in other frames, loading component
+ ad URNs in fenced frames of other frames, etc)
+* adAuctionConfig passed to reportResult().
+* trusted scoring signals.
+* Component ads.
+* Component auctions.
+* browserSignals fields in scoring/bidding methods.
+* In reporting methods, browserSignals fields: dataVersion, topLevelSeller,
+ componentSeller, modifiedBid, adCost, madeHighestScoringOtherBid
+ (with interest group from another origin).
+* Loading ads in iframes.
+* In fencedframes window.fence.setReportEventDataForAutomaticBeacons()
+* Calling leaveAdInterestGroup() in the frame of a winning ad (and one
+ of its component ads)
+* Network timeouts.
+* Validate specific escaping behavior logic (still under discussion). There
+ are a number of different rules for which characters are escaped, and
+ whether spacess are escaped as "%20" or "+".
+* Reports not sent if ad not used.
+* Ties.
+* Interactions with local network access API, which requires public
+ networks to send CORS preflights for requests made over local networks.
+ Needs testing with public publisher pages running auctions with
+ sellers / buyers / update URLs on local networks.
+
+If possible:
+* Aggregate reporting.
+* Join/leave permission delegation via .well-known files.
+* k-anonymity.
+* Signals request batching. This is an optional feature, so can't require it,
+ but maybe a test where batching could be used, and make sure things work,
+ whether batching is used or not?
diff --git a/testing/web-platform/tests/fledge/tentative/auction-config.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/auction-config.https.sub.window.js
new file mode 100644
index 0000000000..1455871dad
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/auction-config.https.sub.window.js
@@ -0,0 +1,214 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+
+"use strict;"
+
+// The tests in this file focus on calls to runAdAuction with various
+// auctionConfigs.
+
+const makeTest = ({
+ // Test name
+ name,
+ // Expectation function (EXPECT_NULL, etc.)
+ expect,
+ // Overrides to the auction config.
+ auctionConfigOverrides = {},
+}) => {
+ promise_test(async test => {
+ const uuid = generateUuid(test);
+ // Join an interest group so the auction actually runs.
+ await joinInterestGroup(test, uuid);
+ let auctionResult;
+ try {
+ auctionResult = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
+ } catch (e) {
+ auctionResult = e;
+ }
+ expect(auctionResult);
+ }, name);
+};
+
+// Expect an unsuccessful auction (yielding null).
+const EXPECT_NO_WINNER = auctionResult => {
+ assert_equals(auctionResult, null, 'Auction unexpected had a winner');
+};
+
+// Expect an exception of the given type.
+const EXPECT_EXCEPTION = exceptionType => auctionResult => {
+ assert_not_equals(auctionResult, null, "got null instead of expected error");
+ assert_true(auctionResult instanceof Error, "did not get expected error: " + auctionResult);
+ assert_throws_js(exceptionType, () => { throw auctionResult; });
+};
+
+makeTest({
+ name: 'no buyers => no winners',
+ expect: EXPECT_NO_WINNER,
+ auctionConfigOverrides: {interestGroupBuyers: []},
+});
+
+makeTest({
+ name: 'seller is not an https URL',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {seller: "ftp://not-https"},
+});
+
+makeTest({
+ name: 'decisionLogicUrl is invalid',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { decisionLogicUrl: "https://foo:99999999999" },
+});
+
+makeTest({
+ name: 'decisionLogicUrl is cross-origin with seller',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { decisionLogicUrl: "https://example.com" },
+});
+
+makeTest({
+ name: 'trustedScoringSignalsUrl is invalid',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { trustedScoringSignalsUrl: "https://foo:99999999999" },
+});
+
+makeTest({
+ name: 'trustedScoringSignalsUrl is cross-origin with seller',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { trustedScoringSignalsUrl: "https://example.com" },
+});
+
+makeTest({
+ name: 'interestGroupBuyer is invalid',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { interestGroupBuyers: ["https://foo:99999999999"] },
+});
+
+makeTest({
+ name: 'interestGroupBuyer is not https',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { interestGroupBuyers: ["http://example.com"] },
+});
+
+makeTest({
+ name: 'only one interestGroupBuyer is invalid',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ interestGroupBuyers: ["https://example.com", "https://foo:99999999999"],
+ },
+});
+
+makeTest({
+ name: 'only one interestGroupBuyer is not https',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ interestGroupBuyers: ["https://example.com", "http://example.com"],
+ },
+});
+
+makeTest({
+ name: 'auctionSignals is invalid as JSON',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { auctionSignals: { sig: BigInt(13) } },
+});
+
+makeTest({
+ name: 'sellerSignals is invalid as JSON',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { sellerSignals: { sig: BigInt(13) } },
+});
+
+makeTest({
+ name: 'directFromSellerSignals is invalid',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { directFromSellerSignals: "https://foo:99999999999" },
+});
+
+makeTest({
+ name: 'directFromSellerSignals is cross-origin with seller',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { directFromSellerSignals: "https://example.com" },
+});
+
+makeTest({
+ name: 'directFromSellerSignals has nonempty query',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { directFromSellerSignals: window.location.origin + "?foo=bar" },
+});
+
+makeTest({
+ name: 'perBuyerSignals has invalid URL in a key',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { perBuyerSignals: { "https://foo:99999999999" : {} }},
+});
+
+makeTest({
+ name: 'perBuyerSignals value is invalid as JSON',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ perBuyerSignals: { "https://example.com" : { sig: BigInt(1) },
+ }},
+});
+
+makeTest({
+ name: 'perBuyerGroupLimits has invalid URL in a key',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { perBuyerGroupLimits: { "https://foo:99999999999" : 5 }},
+});
+
+makeTest({
+ name: 'perBuyerExperimentGroupIds has invalid URL in a key',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: { perBuyerExperimentGroupIds: { "https://foo:99999999999" : 11 }},
+});
+
+makeTest({
+ name: 'perBuyerPrioritySignals has invalid URL in a key',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ perBuyerPrioritySignals: { "https://foo:99999999999" : { sig: 2.5} },
+ },
+});
+
+makeTest({
+ name: 'perBuyerPrioritySignals has a value with a key with prefix "browserSignals"',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ perBuyerPrioritySignals: { "https://example.com" : { "browserSignals.foo" : true } },
+ },
+});
+
+makeTest({
+ name: 'component auctions are not allowed within component auctions',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ interestGroupBuyers: undefined,
+ componentAuctions: [
+ {
+ seller: window.location.origin,
+ decisionLogicUrl: window.location.origin,
+ interestGroupBuyers: undefined,
+ componentAuctions: [
+ {
+ seller: window.location.origin,
+ decisionLogicUrl: window.location.origin,
+ }
+ ],
+ },
+ ],
+ },
+});
+
+makeTest({
+ name: 'component auctions are not allowed with interestGroupBuyers',
+ expect: EXPECT_EXCEPTION(TypeError),
+ auctionConfigOverrides: {
+ interestGroupBuyers: ["https://example.com"],
+ componentAuctions: [
+ {
+ seller: window.location.origin,
+ decisionLogicUrl: window.location.origin,
+ interestGroupBuyers: [],
+ },
+ ],
+ },
+});
diff --git a/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers-insecure-context.tentative.http.html b/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers-insecure-context.tentative.http.html
new file mode 100644
index 0000000000..d3bdb80175
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers-insecure-context.tentative.http.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ promise_test(async t => {
+ return promise_rejects_dom(t, 'NotAllowedError',
+ fetch('./resources/empty.html', {adAuctionHeaders: true}));
+ }, 'test fetch(<url>, {adAuctionHeaders: true}) in insecure context');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers.tentative.https.html b/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers.tentative.https.html
new file mode 100644
index 0000000000..7b2a2c2ba4
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/fetch-ad-auction-headers.tentative.https.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ promise_test(async t => {
+ let response = await fetch('./resources/empty.html', {adAuctionHeaders: true});
+ // TODO(crbug.com/1442274): Check the request header when the functions
+ // are fully implemented.
+ }, 'test fetch(<url>, {adAuctionHeaders: true})');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fledge/tentative/insecure-context.window.js b/testing/web-platform/tests/fledge/tentative/insecure-context.window.js
new file mode 100644
index 0000000000..9016277b73
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/insecure-context.window.js
@@ -0,0 +1,8 @@
+"use strict";
+
+test(() => {
+ assert_false('joinAdInterestGroup' in navigator, 'joinAdInterestGroup not available.');
+ assert_false('leaveAdInterestGroup' in navigator, 'leaveAdInterestGroup not available.');
+ assert_false('runAdAuction' in navigator, 'runAdAuction not available.');
+ assert_false('updateAdInterestGroups' in navigator, 'updateAdInterestGroups not available.');
+}, "Fledge requires secure context.");
diff --git a/testing/web-platform/tests/fledge/tentative/join-leave-ad-interest-group.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/join-leave-ad-interest-group.https.sub.window.js
new file mode 100644
index 0000000000..973c586ca4
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/join-leave-ad-interest-group.https.sub.window.js
@@ -0,0 +1,570 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+
+"use strict;"
+
+// These tests are focused on joinAdInterestGroup() and leaveAdInterestGroup().
+// Most join tests do not run auctions, but instead only check the result of
+// the returned promise, since testing that interest groups are actually
+// joined, and that each interestGroup field behaves as intended, are covered
+// by other tests.
+
+// Minimal fields needed for a valid interest group. Used in most test cases.
+const BASE_INTEREST_GROUP = {
+ owner: window.location.origin,
+ name: 'default name',
+}
+
+// Each test case attempts to join and then leave an interest group, checking
+// if any exceptions are thrown from either operation.
+const SIMPLE_JOIN_LEAVE_TEST_CASES = [
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: null
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: {}
+ },
+
+ // Basic success test case.
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: BASE_INTEREST_GROUP
+ },
+
+ // "owner" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { name: 'default name' }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ owner: null}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ owner: window.location.origin.replace('https', 'http')}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ owner: window.location.origin.replace('https', 'wss')}
+ },
+ // Cross-origin joins and leaves are not allowed without .well-known
+ // permissions.
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ owner: '{{hosts[][www]}}' }
+ },
+
+ // "name" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: false,
+ interestGroup: { owner: window.location.origin }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ name: ''}
+ },
+
+ // "priority" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priority: 1}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priority: 0}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priority: -1.5}
+ },
+
+ // "priorityVector" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: null}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: 1}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: {a: 'apple'}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: {}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: {a: 1}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ priorityVector: {'a': 1, 'b': -4.5, 'a.b': 0}}
+ },
+
+ // "prioritySignalsOverrides" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: null}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: 1}
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: {a: 'apple'}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: {}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: {a: 1}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ prioritySignalsOverrides: {'a': 1, 'b': -4.5, 'a.b': 0}}
+ },
+
+ // "enableBiddingSignalsPrioritization" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ enableBiddingSignalsPrioritization: true}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ enableBiddingSignalsPrioritization: false}
+ },
+
+ // "biddingLogicUrl" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingLogicUrl: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingLogicUrl: 'https://{{hosts[][www]}}/foo.js' }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingLogicUrl: 'data:text/javascript,Foo' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingLogicUrl: `${window.location.origin}/foo.js`}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingLogicUrl: 'relative/path' }
+ },
+
+ // "biddingWasmHelperUrl" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingWasmHelperUrl: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingWasmHelperUrl: 'https://{{hosts[][www]}}/foo.js' }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingWasmHelperUrl: 'data:application/wasm,Foo' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingWasmHelperUrl: `${window.location.origin}/foo.js`}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ biddingWasmHelperUrl: 'relative/path' }
+ },
+
+ // "dailyUpdateUrl" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ dailyUpdateUrl: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ dailyUpdateUrl: 'https://{{hosts[][www]}}/foo.js' }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ dailyUpdateUrl: 'data:application/wasm,Foo' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ dailyUpdateUrl: `${window.location.origin}/foo.js`}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ dailyUpdateUrl: 'relative/path' }
+ },
+
+ // "executionMode" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ executionMode: 'compatibility' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ executionMode: 'groupByOrigin' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ executionMode: 'unknownValuesAreValid' }
+ },
+
+ // "trustedBiddingSignalsUrl" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsUrl: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsUrl: 'https://{{hosts[][www]}}/foo.js' }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsUrl: 'data:application/json,{}' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsUrl: `${window.location.origin}/foo.js`}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsUrl: 'relative/path' }
+ },
+
+ // "trustedBiddingSignalsKeys" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsKeys: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsKeys: {}}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsKeys: []}
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ trustedBiddingSignalsKeys: ['a', 4, 'Foo']}
+ },
+
+ // "userBiddingSignals" tests
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ userBiddingSignals: null }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ userBiddingSignals: 'foo' }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ userBiddingSignals: 15 }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ userBiddingSignals: [5, 'foo', [-6.4, {a: 'b'}]] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ userBiddingSignals: {a: [5, 'foo', {b: -6.4}] }}
+ },
+
+ // "ads" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: 5 }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: {} }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [] }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{}] }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{metadata: [{a:'b'}, 'c'], 1:[2,3]}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{renderUrl: 'https://somewhere.test/'}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{renderUrl: 'https://somewhere.test/'},
+ {renderUrl: 'https://somewhere-else.test/'}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{renderUrl: 'https://somewhere.test/',
+ metadata: null}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ ads: [{renderUrl: 'https://somewhere.test/',
+ metadata: null,
+ someOtherField: 'foo'}] }
+ },
+
+ // "adComponents" tests
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: null }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: 5 }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: {} }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [] }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{}] }
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{metadata: [{a:'b'}, 'c'], 1:[2,3]}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{renderUrl: 'https://somewhere.test/'}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{renderUrl: 'https://somewhere.test/'},
+ {renderUrl: 'https://elsewhere.test/'}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{renderUrl: 'https://somewhere.test/',
+ metadata: null}] }
+ },
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ adComponents: [{renderUrl: 'https://somewhere.test/',
+ metadata: null,
+ someOtherField: 'foo'}] }
+ },
+
+ // Miscellaneous tests.
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ extra: false,
+ fields: {do:'not'},
+ matter: 'at',
+ all: [3,4,5] }
+ },
+
+ // Interest group dictionaries must be less than 1 MB (1048576 bytes), so
+ // test that here by using a large name on an otherwise valid interest group
+ // dictionary. The first case is the largest name value that still results in
+ // a valid dictionary, whereas the second test case produces a dictionary
+ // that's one byte too large.
+ { expectJoinSucces: true,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ name: 'a'.repeat(1048528) },
+ testCaseName: "Largest possible interest group dictionary",
+ },
+ { expectJoinSucces: false,
+ expectLeaveSucces: true,
+ interestGroup: { ...BASE_INTEREST_GROUP,
+ name: 'a'.repeat(1048529) },
+ testCaseName: "Oversized interest group dictionary",
+ },
+];
+
+for (testCase of SIMPLE_JOIN_LEAVE_TEST_CASES) {
+ var test_name = 'Join and leave interest group: ';
+ if ('testCaseName' in testCase) {
+ test_name += testCase.testCaseName;
+ } else {
+ test_name += JSON.stringify(testCase);
+ }
+
+ promise_test((async (testCase) => {
+ const INTEREST_GROUP_LIFETIME_SECS = 1;
+
+ let join_promise = navigator.joinAdInterestGroup(testCase.interestGroup,
+ INTEREST_GROUP_LIFETIME_SECS);
+ assert_true(join_promise instanceof Promise, "join should return a promise");
+ if (testCase.expectJoinSucces) {
+ assert_equals(await join_promise, undefined);
+ } else {
+ let joinExceptionThrown = false;
+ try {
+ await join_promise;
+ } catch (e) {
+ joinExceptionThrown = true;
+ }
+ assert_true(joinExceptionThrown, 'Exception not thrown on join.');
+ }
+
+ let leave_promise = navigator.leaveAdInterestGroup(testCase.interestGroup);
+ assert_true(leave_promise instanceof Promise, "leave should return a promise");
+ if (testCase.expectLeaveSucces) {
+ assert_equals(await leave_promise, undefined);
+ } else {
+ let leaveExceptionThrown = false;
+ try {
+ await leave_promise;
+ } catch (e) {
+ leaveExceptionThrown = true;
+ }
+ assert_true(leaveExceptionThrown, 'Exception not thrown on leave.');
+ }
+ }).bind(undefined, testCase), test_name);
+}
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+
+ // Joining an interest group without a bidding script and run an auction.
+ // There should be no winner.
+ await joinInterestGroup(test, uuid, { biddingLogicUrl: null });
+ assert_equals(null, await runBasicFledgeAuction(test, uuid),
+ 'Auction unexpectedly had a winner');
+
+ // Joining an interest group with a bidding script and the same owner/name as
+ // the previously joined interest group, and re-run the auction. There should
+ // be a winner this time.
+ await joinInterestGroup(test, uuid);
+ let config = await runBasicFledgeAuction(test, uuid);
+ assert_true(config instanceof FencedFrameConfig,
+ 'Wrong value type returned from auction: ' +
+ config.constructor.name);
+
+ // Re-join the first interest group, and re-run the auction. The interest
+ // group should be overwritten again, and there should be no winner.
+ await joinInterestGroup(test, uuid, { biddingLogicUrl: null });
+ assert_equals(null, await runBasicFledgeAuction(test, uuid),
+ 'Auction unexpectedly had a winner');
+}, 'Join same interest group overwrites old matching group.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+
+ // Join an interest group, run an auction to make sure it was joined.
+ await joinInterestGroup(test, uuid);
+ let config = await runBasicFledgeAuction(test, uuid);
+ assert_true(config instanceof FencedFrameConfig,
+ 'Wrong value type returned from auction: ' +
+ config.constructor.name);
+
+ // Leave the interest group, re-run the auction. There should be no winner.
+ await leaveInterestGroup();
+ assert_equals(null, await runBasicFledgeAuction(test, uuid),
+ 'Auction unexpectedly had a winner');
+}, 'Leaving interest group actually leaves interest group.');
+
+promise_test(async test => {
+ // This should not throw.
+ await leaveInterestGroup({ name: 'Never join group' });
+}, 'Leave an interest group that was never joined.');
diff --git a/testing/web-platform/tests/fledge/tentative/no-winner.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/no-winner.https.sub.window.js
new file mode 100644
index 0000000000..8fd7df50cd
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/no-winner.https.sub.window.js
@@ -0,0 +1,80 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict;"
+
+// The tests in this file focus on simple auctions (one bidder, one seller, one
+// origin, one frame) which have no winning bid, either due to errors or due to
+// there being no bids, except where tests fit better with another set of tests.
+
+// Errors common to bidding and decision logic scripts. These atrings will be
+// appended to script URLs to make the python scripts that generate bidding
+// logic and decision logic scripts with errors.
+const COMMON_SCRIPT_ERRORS = [
+ 'error=close-connection',
+ 'error=http-error',
+ 'error=no-content-type',
+ 'error=wrong-content-type',
+ 'error=bad-allow-fledge',
+ 'error=fledge-not-allowed',
+ 'error=no-allow-fledge',
+ 'error=no-body',
+];
+
+const BIDDING_LOGIC_SCRIPT_ERRORS = [
+ ...COMMON_SCRIPT_ERRORS,
+ 'error=no-generateBid',
+ 'generateBid=throw 1;',
+ 'generateBid=This does not compile',
+ // Default timeout test. Doesn't check how long timing out takes.
+ 'generateBid=while(1);',
+ // Bad return values:
+ 'generateBid=return 5;',
+ 'generateBid=return "Foo";',
+ 'generateBid=return interestGroup.ads[0].renderUrl;',
+ 'generateBid=return {bid: 1, render: "https://not-in-ads-array.test/"};',
+ 'generateBid=return {bid: 1};',
+ 'generateBid=return {render: interestGroup.ads[0].renderUrl};',
+ // These are not bidding rather than errors.
+ 'generateBid=return {bid:0, render: interestGroup.ads[0].renderUrl};',
+ 'generateBid=return {bid:-1, render: interestGroup.ads[0].renderUrl};',
+];
+
+const DECISION_LOGIC_SCRIPT_ERRORS = [
+ ...COMMON_SCRIPT_ERRORS,
+ 'error=no-scoreAd',
+ 'scoreAd=throw 1;',
+ 'scoreAd=This does not compile',
+ // Default timeout test. Doesn't check how long timing out takes.
+ 'scoreAd=while(1);',
+ // Bad return values:
+ 'scoreAd=return "Foo";',
+ 'scoreAd=return {desirability: "Foo"};',
+ // These are rejecting the bid rather than errors.
+ 'scoreAd=return 0;',
+ 'scoreAd=return -1;',
+ 'scoreAd=return {desirability: 0};',
+ 'scoreAd=return {desirability: -1};',
+];
+
+for (error of BIDDING_LOGIC_SCRIPT_ERRORS) {
+ promise_test((async (error, test) => {
+ let biddingLogicUrl = `${BASE_URL}resources/bidding-logic.sub.py?${error}`;
+ await runBasicFledgeTestExpectingNoWinner(
+ test,
+ {interestGroupOverrides: {biddingLogicUrl: biddingLogicUrl}}
+ );
+ }).bind(undefined, error), `Bidding logic script: ${error}`);
+}
+
+for (error of DECISION_LOGIC_SCRIPT_ERRORS) {
+ promise_test((async (error, test) => {
+ let decisionLogicUrl =
+ `${BASE_URL}resources/decision-logic.sub.py?${error}`;
+ await runBasicFledgeTestExpectingNoWinner(
+ test, {auctionConfigOverrides: {decisionLogicUrl: decisionLogicUrl}}
+ );
+ }).bind(undefined, error), `Decision logic script: ${error}`);
+}
diff --git a/testing/web-platform/tests/fledge/tentative/register-ad-beacon.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/register-ad-beacon.https.sub.window.js
new file mode 100644
index 0000000000..2284ac85b4
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/register-ad-beacon.https.sub.window.js
@@ -0,0 +1,323 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict;"
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ '',
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: `],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "",
+ destination: ["seller"]
+ });`)
+ );
+}, 'Seller calls registerAdBeacon().');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ '',
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createBidderBeaconUrl(uuid)}, body: `],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "",
+ destination: ["buyer"]
+ });`)
+ );
+}, 'Buyer calls registerAdBeacon().');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ '',
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: body`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body",
+ destination: ["seller"]
+ });`)
+ );
+}, 'Seller calls registerAdBeacon(), beacon sent with body.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ '',
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createBidderBeaconUrl(uuid)}, body: body`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body",
+ destination: ["buyer"]
+ });`)
+ );
+}, 'Buyer calls registerAdBeacon(), beacon sent with body.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ '',
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: body1`,
+ `${createSellerBeaconUrl(uuid)}, body: body2`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body1",
+ destination: ["seller"]
+ });
+ window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body2",
+ destination: ["seller"]
+ });`)
+ );
+}, 'Seller calls registerAdBeacon(). reportEvent() called twice.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ '',
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createBidderBeaconUrl(uuid)}, body: body1`,
+ `${createBidderBeaconUrl(uuid)}, body: body2`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body1",
+ destination: ["buyer"]
+ });
+ window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body2",
+ destination: ["buyer"]
+ });`)
+ );
+}, 'Buyer calls registerAdBeacon(). reportEvent() called twice.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon1: '${createSellerBeaconUrl(uuid, '1')}',
+ beacon2: '${createSellerBeaconUrl(uuid, '2')}'});`,
+ // reportWin:
+ null,
+ '',
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid, '1')}, body: body1`,
+ `${createSellerBeaconUrl(uuid, '2')}, body: body2`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon1",
+ eventData: "body1",
+ destination: ["seller"]
+ });
+ window.fence.reportEvent({
+ eventType: "beacon2",
+ eventData: "body2",
+ destination: ["seller"]
+ });`)
+ );
+}, 'Seller calls registerAdBeacon() with multiple beacons.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ '',
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon1: '${createBidderBeaconUrl(uuid, '1')}',
+ beacon2: '${createBidderBeaconUrl(uuid, '2')}'});`,
+ // expectedReportUrls:
+ [`${createBidderBeaconUrl(uuid, '1')}, body: body1`,
+ `${createBidderBeaconUrl(uuid, '2')}, body: body2`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon1",
+ eventData: "body1",
+ destination: ["buyer"]
+ });
+ window.fence.reportEvent({
+ eventType: "beacon2",
+ eventData: "body2",
+ destination: ["buyer"]
+ });`)
+ );
+}, 'Buyer calls registerAdBeacon() with multiple beacons.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: body`,
+ `${createBidderBeaconUrl(uuid)}, body: body`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body",
+ destination: ["seller","buyer"]
+ });`)
+ );
+}, 'Seller and buyer call registerAdBeacon() with shared reportEvent() call.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: body1`,
+ `${createBidderBeaconUrl(uuid)}, body: body2`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body1",
+ destination: ["seller"]
+ });
+ window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body2",
+ destination: ["buyer"]
+ });`)
+ );
+}, 'Seller and buyer call registerAdBeacon() with separate reportEvent() calls.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ // Multiple registerAdBeacon() call should result in an exception,
+ // throwing away all beacons and other types of reports.
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});
+ registerAdBeacon({beacon1: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ 'sellerSignals === null',
+ `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createBidderBeaconUrl(uuid)}, body: body`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body",
+ destination: ["seller","buyer"]
+ });`)
+ );
+}, 'Seller calls registerAdBeacon() multiple times.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`,
+ // reportWin:
+ null,
+ // Multiple registerAdBeacon() call should result in an exception,
+ // throwing away all beacons and other types of reports.
+ `sendReportTo('${createBidderReportUrl(uuid)}');
+ registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});
+ registerAdBeacon({beacon1: '${createBidderBeaconUrl(uuid)}'});`,
+ // expectedReportUrls:
+ [`${createSellerBeaconUrl(uuid)}, body: body`],
+ // renderUrlOverride:
+ createRenderUrl(
+ uuid,
+ `window.fence.reportEvent({
+ eventType: "beacon",
+ eventData: "body",
+ destination: ["seller","buyer"]
+ });`)
+ );
+}, 'Buyer calls registerAdBeacon() multiple times.');
diff --git a/testing/web-platform/tests/fledge/tentative/reporting-arguments.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/reporting-arguments.https.sub.window.js
new file mode 100644
index 0000000000..262e0eb87e
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/reporting-arguments.https.sub.window.js
@@ -0,0 +1,291 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict;"
+
+// Simplified version of reportTest() for validating arguments to reporting
+// methods. Only takes expressions to check in reporting methods. "uuid" is
+// optional, and one is generated if not passed one.
+async function runReportArgumentValidationTest(
+ test, reportResultSuccessCondition, reportWinSuccessCondition, uuid) {
+ if (!uuid)
+ uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ reportResultSuccessCondition,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ reportWinSuccessCondition,
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}
+
+/////////////////////////////////////////////////////////////////////
+// reportResult() to reportWin() message passing tests
+/////////////////////////////////////////////////////////////////////
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ return 45;`,
+ // reportWin:
+ 'sellerSignals === 45',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}, 'Seller passes number to bidder.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ return 'foo';`,
+ // reportWin:
+ 'sellerSignals === "foo"',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}, 'Seller passes string to bidder.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ return [3, 1, 2];`,
+ // reportWin:
+ 'JSON.stringify(sellerSignals) === "[3,1,2]"',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}, 'Seller passes array to bidder.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ return {a: 4, b:['c', null, {}]};`,
+ // reportWin:
+ `JSON.stringify(sellerSignals) === '{"a":4,"b":["c",null,{}]}'`,
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}, 'Seller passes object to bidder.');
+
+/////////////////////////////////////////////////////////////////////
+// reportResult() / reportWin() browserSignals tests.
+/////////////////////////////////////////////////////////////////////
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.topWindowHostname === "${window.location.hostname}"`,
+ // reportWinSuccessCondition:
+ `browserSignals.topWindowHostname === "${window.location.hostname}"`
+ );
+}, 'browserSignals.topWindowHostname test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.seller === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.seller === "${window.location.origin}"`
+ );
+}, 'browserSignals.seller test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.topLevelSeller === undefined &&
+ browserSignals.componentSeller === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.topLevelSeller === undefined &&
+ browserSignals.componentSeller === undefined`
+ );
+}, 'browserSignals.topLevelSeller and browserSignals.componentSeller test.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.renderUrl === "${createRenderUrl(uuid)}"`,
+ // reportWinSuccessCondition:
+ `browserSignals.renderUrl === "${createRenderUrl(uuid)}"`,
+ uuid
+ );
+}, 'browserSignals.renderUrl test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.bid === 9`,
+ // reportWinSuccessCondition:
+ `browserSignals.bid === 9`
+ );
+}, 'browserSignals.bid test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.desirability === 18`,
+ // reportWinSuccessCondition:
+ `browserSignals.desirability === undefined`
+ );
+}, 'browserSignals.desirability test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.topLevelSellerSignals === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.topLevelSellerSignals === undefined`
+ );
+}, 'browserSignals.topLevelSellerSignals test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.dataVersion === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.dataVersion === undefined`
+ );
+}, 'browserSignals.dataVersion test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.modifiedBid === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.modifiedBid === undefined`
+ );
+}, 'browserSignals.modifiedBid test.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 0`,
+ // reportWinSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 0`,
+ uuid
+ );
+}, 'browserSignals.highestScoringOtherBid with no other interest groups test.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: -2}),
+ name: 'other interest group 1' });
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: -1}),
+ name: 'other interest group 2' });
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 0`,
+ // reportWinSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 0`,
+ uuid
+ );
+}, 'browserSignals.highestScoringOtherBid with other groups that do not bid.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: 2}),
+ name: 'other interest group 1' });
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: 5}),
+ name: 'other interest group 2' });
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: 2}),
+ name: 'other interest group 3' });
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 5`,
+ // reportWinSuccessCondition:
+ `browserSignals.highestScoringOtherBid === 5`,
+ uuid
+ );
+}, 'browserSignals.highestScoringOtherBid with other bids.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.interestGroupName === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.interestGroupName === "default name"`
+ );
+}, 'browserSignals.interestGroupName test.');
+
+promise_test(async test => {
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === false`
+ );
+}, 'browserSignals.madeHighestScoringOtherBid with no other bids.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: -1}),
+ name: 'other interest group 2' });
+ await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === false`
+ );
+}, 'browserSignals.madeHighestScoringOtherBid with group that did not bid.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid,
+ { biddingLogicUrl: createBiddingScriptUrl({bid: 1}),
+ name: 'other interest group 2' });
+await runReportArgumentValidationTest(
+ test,
+ // reportResultSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === undefined`,
+ // reportWinSuccessCondition:
+ `browserSignals.madeHighestScoringOtherBid === true`,
+ uuid
+ );
+}, 'browserSignals.madeHighestScoringOtherBid with other bid.');
diff --git a/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py b/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py
new file mode 100644
index 0000000000..6b4e179cff
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py
@@ -0,0 +1,57 @@
+# General bidding logic script. Depending on query parameters, it can
+# simulate a variety of network errors, and its generateBid() and
+# reportWin() functions can have arbitrary Javascript code injected
+# in them. generateBid() will by default return a bid of 9 for the
+# first ad.
+def main(request, response):
+ error = request.GET.first(b"error", None)
+
+ if error == b"close-connection":
+ # Close connection without writing anything, to simulate a network
+ # error. The write call is needed to avoid writing the default headers.
+ response.writer.write("")
+ response.close_connection = True
+ return
+
+ if error == b"http-error":
+ response.status = (404, b"OK")
+ else:
+ response.status = (200, b"OK")
+
+ if error == b"wrong-content-type":
+ response.headers.set(b"Content-Type", b"application/json")
+ elif error != b"no-content-type":
+ response.headers.set(b"Content-Type", b"application/javascript")
+
+ if error == b"bad-allow-fledge":
+ response.headers.set(b"X-Allow-FLEDGE", b"sometimes")
+ elif error == b"fledge-not-allowed":
+ response.headers.set(b"X-Allow-FLEDGE", b"false")
+ elif error != b"no-allow-fledge":
+ response.headers.set(b"X-Allow-FLEDGE", b"true")
+
+ body = b''
+ if error == b"no-body":
+ return body
+ if error != b"no-generateBid":
+ body += b"""
+ function generateBid(interestGroup, auctionSignals, perBuyerSignals,
+ trustedBiddingSignals, browserSignals,
+ directFromSellerSignals) {
+ {{GET[generateBid]}};
+ return {
+ 'bid': 9,
+ 'render': interestGroup.ads[0].renderUrl
+ };
+ }"""
+ bid = request.GET.first(b"bid", None)
+ if bid != None:
+ body = body.replace(b"9", bid)
+ if error != b"no-reportWin":
+ body += b"""
+ function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
+ browserSignals, directFromSellerSignals) {
+ {{GET[reportWin]}};
+ }"""
+ return body
+
diff --git a/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py b/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py
new file mode 100644
index 0000000000..9a97d45bd1
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py
@@ -0,0 +1,54 @@
+# General decision logic script. Depending on query parameters, it can
+# simulate a variety of network errors, and its scoreAd() and
+# reportResult() functions can have arbitrary Javascript code injected
+# in them. scoreAd() will by default return a desirability score of
+# twice the bid for each ad, as long as the ad URL ends with the uuid.
+def main(request, response):
+ error = request.GET.first(b"error", None)
+
+ if error == b"close-connection":
+ # Close connection without writing anything, to simulate a network
+ # error. The write call is needed to avoid writing the default headers.
+ response.writer.write("")
+ response.close_connection = True
+ return
+
+ if error == b"http-error":
+ response.status = (404, b"OK")
+ else:
+ response.status = (200, b"OK")
+
+ if error == b"wrong-content-type":
+ response.headers.set(b"Content-Type", b"application/json")
+ elif error != b"no-content-type":
+ response.headers.set(b"Content-Type", b"application/javascript")
+
+ if error == b"bad-allow-fledge":
+ response.headers.set(b"X-Allow-FLEDGE", b"sometimes")
+ elif error == b"fledge-not-allowed":
+ response.headers.set(b"X-Allow-FLEDGE", b"false")
+ elif error != b"no-allow-fledge":
+ response.headers.set(b"X-Allow-FLEDGE", b"true")
+
+ body = b''
+ if error == b"no-body":
+ return body
+ if error != b"no-scoreAd":
+ body += b"""
+ function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals,
+ browserSignals) {
+ // Don't bid on interest group with the wrong uuid. This is to prevent
+ // left over interest groups from other tests from affecting auction
+ // results.
+ if (!browserSignals.renderUrl.endsWith('{{GET[uuid]}}'))
+ return 0;
+
+ {{GET[scoreAd]}};
+ return {desirability: 2 * bid, allowComponentAuction: true};
+ }"""
+ if error != b"no-reportResult":
+ body += b"""
+ function reportResult(auctionConfig, browserSignals, directFromSellerSignals) {
+ {{GET[reportResult]}};
+ }"""
+ return body
diff --git a/testing/web-platform/tests/fledge/tentative/resources/empty.html b/testing/web-platform/tests/fledge/tentative/resources/empty.html
new file mode 100644
index 0000000000..0e76edd65b
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE html>
diff --git a/testing/web-platform/tests/fledge/tentative/resources/fenced-frame.sub.py b/testing/web-platform/tests/fledge/tentative/resources/fenced-frame.sub.py
new file mode 100644
index 0000000000..c29bb6fecc
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/fenced-frame.sub.py
@@ -0,0 +1,19 @@
+# Fenced frame HTML body. Generated by a Python file to avoid having quotes in
+# the injected script escaped, which the test server does to *.html files.
+def main(request, response):
+ response.status = (200, b"OK")
+ response.headers.set(b"Content-Type", b"text/html")
+ response.headers.set(b"Supports-Loading-Mode", b"fenced-frame")
+
+ return """
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <script>
+ {{GET[script]}}
+ </script>
+ </body>
+ </html>
+ """
+
+
diff --git a/testing/web-platform/tests/fledge/tentative/resources/fledge-util.js b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.js
new file mode 100644
index 0000000000..5a05764e74
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.js
@@ -0,0 +1,319 @@
+"use strict;"
+
+const FULL_URL = window.location.href;
+const BASE_URL = FULL_URL.substring(0, FULL_URL.lastIndexOf('/') + 1);
+const BASE_PATH = (new URL(BASE_URL)).pathname;
+
+const DEFAULT_INTEREST_GROUP_NAME = 'default name';
+
+// Unlike other URLs, the trustedBiddingSignalsUrl can't have a query string
+// that's set by tests, since FLEDGE controls it entirely, so tests that
+// exercise it use a fixed URL string. Special keys and interest group names
+// control the response.
+const TRUSTED_BIDDING_SIGNALS_URL =
+ `${BASE_URL}resources/trusted-bidding-signals.py`;
+
+// Creates a URL that will be sent to the URL request tracker script.
+// `uuid` is used to identify the stash shard to use.
+// `dispatch` affects what the tracker script does.
+// `id` can be used to uniquely identify tracked requests. It has no effect
+// on behavior of the script; it only serves to make the URL unique.
+function createTrackerUrl(origin, uuid, dispatch, id = null) {
+ let url = new URL(`${origin}${BASE_PATH}resources/request-tracker.py`);
+ url.searchParams.append('uuid', uuid);
+ url.searchParams.append('dispatch', dispatch);
+ if (id)
+ url.searchParams.append('id', id);
+ return url.toString();
+}
+
+// Create tracked bidder/seller URLs. The only difference is the prefix added
+// to the `id` passed to createTrackerUrl. The optional `id` field allows
+// multiple bidder/seller report URLs to be distinguishable from each other.
+function createBidderReportUrl(uuid, id = '1') {
+ return createTrackerUrl(window.location.origin, uuid, `track_get`,
+ `bidder_report_${id}`);
+}
+function createSellerReportUrl(uuid, id = '1') {
+ return createTrackerUrl(window.location.origin, uuid, `track_get`,
+ `seller_report_${id}`);
+}
+
+// Much like above ReportUrl methods, except designed for beacons, which
+// are expected to be POSTs.
+function createBidderBeaconUrl(uuid, id = '1') {
+ return createTrackerUrl(window.location.origin, uuid, `track_post`,
+ `bidder_beacon_${id}`);
+}
+function createSellerBeaconUrl(uuid, id = '1') {
+ return createTrackerUrl(window.location.origin, uuid, `track_post`,
+ `seller_beacon_${id}`);
+}
+
+// Generates a UUID and registers a cleanup method with the test fixture to
+// request a URL from the request tracking script that clears all data
+// associated with the generated uuid when requested.
+function generateUuid(test) {
+ let uuid = token();
+ test.add_cleanup(async () => {
+ let cleanupUrl = createTrackerUrl(window.location.origin, uuid, 'clean_up');
+ let response = await fetch(cleanupUrl, {credentials: 'omit', mode: 'cors'});
+ assert_equals(await response.text(), 'cleanup complete',
+ `Sever state cleanup failed`);
+ });
+ return uuid;
+}
+
+// Repeatedly requests "request_list" URL until exactly the entries in
+// "expectedRequests" have been observed by the request tracker script (in
+// any order, since report URLs are not guaranteed to be sent in any order).
+//
+// Elements of `expectedRequests` should either be URLs, in the case of GET
+// requests, or "<URL>, body: <body>" in the case of POST requests.
+//
+// If any other strings are received from the tracking script, or the tracker
+// script reports an error, fails the test.
+async function waitForObservedRequests(uuid, expectedRequests) {
+ let trackedRequestsUrl = createTrackerUrl(window.location.origin, uuid,
+ 'request_list');
+ // Sort array for easier comparison, since order doesn't matter.
+ expectedRequests.sort();
+ while (true) {
+ let response = await fetch(trackedRequestsUrl,
+ {credentials: 'omit', mode: 'cors'});
+ let trackerData = await response.json();
+
+ // Fail on fetch error.
+ if (trackerData.error) {
+ throw trackedRequestsUrl + ' fetch failed:' +
+ JSON.stringify(trackerData);
+ }
+
+ // Fail on errors reported by the tracker script.
+ if (trackerData.errors.length > 0) {
+ throw 'Errors reported by request-tracker.py:' +
+ JSON.stringify(trackerData.errors);
+ }
+
+ // If expected number of requests have been observed, compare with list of
+ // all expected requests and exit.
+ let trackedRequests = trackerData.trackedRequests;
+ if (trackedRequests.length == expectedRequests.length) {
+ assert_array_equals(trackedRequests.sort(), expectedRequests);
+ break;
+ }
+
+ // If fewer than total number of expected requests have been observed,
+ // compare what's been received so far, to have a greater chance to fail
+ // rather than hang on error.
+ for (const trackedRequest of trackedRequests) {
+ assert_in_array(trackedRequest, expectedRequests);
+ }
+ }
+}
+
+// Creates a bidding script with the provided code in the method bodies. The
+// bidding script's generateBid() method will return a bid of 9 for the first
+// ad, after the passed in code in the "generateBid" input argument has been
+// run, unless it returns something or throws.
+//
+// The default reportWin() method is empty.
+function createBiddingScriptUrl(params = {}) {
+ let url = new URL(`${BASE_URL}resources/bidding-logic.sub.py`);
+ if (params.generateBid)
+ url.searchParams.append('generateBid', params.generateBid);
+ if (params.reportWin)
+ url.searchParams.append('reportWin', params.reportWin);
+ if (params.error)
+ url.searchParams.append('error', params.error);
+ if (params.bid)
+ url.searchParams.append('bid', params.bid);
+ return url.toString();
+}
+
+// Creates a decision script with the provided code in the method bodies. The
+// decision script's scoreAd() method will reject ads with renderUrls that
+// don't ends with "uuid", and will return a score equal to the bid, after the
+// passed in code in the "scoreAd" input argument has been run, unless it
+// returns something or throws.
+//
+// The default reportResult() method is empty.
+function createDecisionScriptUrl(uuid, params = {}) {
+ let url = new URL(`${BASE_URL}resources/decision-logic.sub.py`);
+ url.searchParams.append('uuid', uuid);
+ if (params.scoreAd)
+ url.searchParams.append('scoreAd', params.scoreAd);
+ if (params.reportResult)
+ url.searchParams.append('reportResult', params.reportResult);
+ if (params.error)
+ url.searchParams.append('error', params.error);
+ return url.toString();
+}
+
+// Creates a renderUrl for an ad that runs the passed in "script". "uuid" has
+// no effect, beyond making the URL distinct between tests, and being verified
+// by the decision logic script before accepting a bid. "uuid" is expected to
+// be last.
+function createRenderUrl(uuid, script) {
+ let url = new URL(`${BASE_URL}resources/fenced-frame.sub.py`);
+ if (script)
+ url.searchParams.append('script', script);
+ url.searchParams.append('uuid', uuid);
+ return url.toString();
+}
+
+// Joins an interest group that, by default, is owned by the current frame's
+// origin, is named DEFAULT_INTEREST_GROUP_NAME, has a bidding script that
+// issues a bid of 9 with a renderUrl of "https://not.checked.test/${uuid}",
+// and sends a report to createBidderReportUrl(uuid) if it wins. Waits for the
+// join command to complete. Adds cleanup command to `test` to leave the
+// interest group when the test completes.
+//
+// `interestGroupOverrides` may be used to override fields in the joined
+// interest group.
+async function joinInterestGroup(test, uuid, interestGroupOverrides = {}) {
+ const INTEREST_GROUP_LIFETIME_SECS = 60;
+
+ let interestGroup = {
+ owner: window.location.origin,
+ name: DEFAULT_INTEREST_GROUP_NAME,
+ biddingLogicUrl: createBiddingScriptUrl(
+ { reportWin: `sendReportTo('${createBidderReportUrl(uuid)}');` }),
+ ads: [{renderUrl: createRenderUrl(uuid)}],
+ ...interestGroupOverrides
+ };
+
+ await navigator.joinAdInterestGroup(interestGroup,
+ INTEREST_GROUP_LIFETIME_SECS);
+ test.add_cleanup(
+ async () => {await navigator.leaveAdInterestGroup(interestGroup)});
+}
+
+// Similar to joinInterestGroup, but leaves the interest group instead.
+// Generally does not need to be called manually when using
+// "joinInterestGroup()".
+async function leaveInterestGroup(interestGroupOverrides = {}) {
+ let interestGroup = {
+ owner: window.location.origin,
+ name: DEFAULT_INTEREST_GROUP_NAME,
+ ...interestGroupOverrides
+ };
+
+ await navigator.leaveAdInterestGroup(interestGroup);
+}
+
+// Runs a FLEDGE auction and returns the result. By default, the seller is the
+// current frame's origin, and the only buyer is as well. The seller script
+// rejects bids for URLs that don't contain "uuid" (to avoid running into issues
+// with any interest groups from other tests), and reportResult() sends a report
+// to createSellerReportUrl(uuid).
+//
+// `auctionConfigOverrides` may be used to override fields in the auction
+// configuration.
+async function runBasicFledgeAuction(test, uuid, auctionConfigOverrides = {}) {
+ let auctionConfig = {
+ seller: window.location.origin,
+ decisionLogicUrl: createDecisionScriptUrl(
+ uuid,
+ { reportResult: `sendReportTo('${createSellerReportUrl(uuid)}');` }),
+ interestGroupBuyers: [window.location.origin],
+ resolveToConfig: true,
+ ...auctionConfigOverrides
+ };
+ return await navigator.runAdAuction(auctionConfig);
+}
+
+// Calls runBasicFledgeAuction(), expecting the auction to have a winner.
+// Creates a fenced frame that will be destroyed on completion of "test", and
+// navigates it to the URN URL returned by the auction. Does not wait for the
+// fenced frame to finish loading, since there's no API that can do that.
+async function runBasicFledgeAuctionAndNavigate(test, uuid,
+ auctionConfigOverrides = {}) {
+ let config = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
+ assert_true(config instanceof FencedFrameConfig,
+ `Wrong value type returned from auction: ${config.constructor.type}`);
+
+ let fencedFrame = document.createElement('fencedframe');
+ fencedFrame.mode = 'opaque-ads';
+ fencedFrame.config = config;
+ document.body.appendChild(fencedFrame);
+ test.add_cleanup(() => { document.body.removeChild(fencedFrame); });
+}
+
+// Joins an interest group and runs an auction, expecting a winner to be
+// returned. "testConfig" can optionally modify the interest group or
+// auctionConfig.
+async function runBasicFledgeTestExpectingWinner(test, testConfig = {}) {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid, testConfig.interestGroupOverrides);
+ let config = await runBasicFledgeAuction(
+ test, uuid, testConfig.auctionConfigOverrides);
+ assert_true(config instanceof FencedFrameConfig,
+ `Wrong value type returned from auction: ${config.constructor.type}`);
+}
+
+// Joins an interest group and runs an auction, expecting no winner to be
+// returned. "testConfig" can optionally modify the interest group or
+// auctionConfig.
+async function runBasicFledgeTestExpectingNoWinner(test, testConfig = {}) {
+ const uuid = generateUuid(test);
+ await joinInterestGroup(test, uuid, testConfig.interestGroupOverrides);
+ let result = await runBasicFledgeAuction(
+ test, uuid, testConfig.auctionConfigOverrides);
+ assert_true(result === null, 'Auction unexpectedly had a winner');
+}
+
+// Test helper for report phase of auctions that lets the caller specify the
+// body of reportResult() and reportWin(). Passing in null will cause there
+// to be no reportResult() or reportWin() method.
+//
+// If the "SuccessCondition" fields are non-null and evaluate to false in
+// the corresponding reporting method, the report is sent to an error URL.
+// Otherwise, the corresponding 'reportResult' / 'reportWin' values are run.
+//
+// `renderUrlOverride` allows the ad URL of the joined InterestGroup to
+// to be set by the caller.
+//
+// Requesting error report URLs causes waitForObservedRequests() to throw
+// rather than hang.
+async function runReportTest(test, uuid, reportResultSuccessCondition,
+ reportResult, reportWinSuccessCondition, reportWin,
+ expectedReportUrls, renderUrlOverride) {
+ if (reportResultSuccessCondition) {
+ reportResult = `if (!(${reportResultSuccessCondition})) {
+ sendReportTo('${createSellerReportUrl(uuid, 'error')}');
+ return false;
+ }
+ ${reportResult}`;
+ }
+ let decisionScriptUrlParams = {};
+ if (reportResult !== null)
+ decisionScriptUrlParams.reportResult = reportResult;
+ else
+ decisionScriptUrlParams.error = 'no-reportResult';
+
+ if (reportWinSuccessCondition) {
+ reportWin = `if (!(${reportWinSuccessCondition})) {
+ sendReportTo('${createSellerReportUrl(uuid, 'error')}');
+ return false;
+ }
+ ${reportWin}`;
+ }
+ let biddingScriptUrlParams = {};
+ if (reportWin !== null)
+ biddingScriptUrlParams.reportWin = reportWin;
+ else
+ biddingScriptUrlParams.error = 'no-reportWin';
+
+ let interestGroupOverrides =
+ { biddingLogicUrl: createBiddingScriptUrl(biddingScriptUrlParams) };
+ if (renderUrlOverride)
+ interestGroupOverrides.ads = [{renderUrl: renderUrlOverride}]
+
+ await joinInterestGroup(test, uuid, interestGroupOverrides);
+ await runBasicFledgeAuctionAndNavigate(
+ test, uuid,
+ { decisionLogicUrl: createDecisionScriptUrl(
+ uuid, decisionScriptUrlParams) });
+ await waitForObservedRequests(uuid, expectedReportUrls);
+}
diff --git a/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py b/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py
new file mode 100644
index 0000000000..46da796f30
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py
@@ -0,0 +1,97 @@
+import mimetypes
+import os
+import json
+import wptserve.stash
+
+from wptserve.utils import isomorphic_decode, isomorphic_encode
+
+# Test server that tracks requests it has previously seen, keyed by a token.
+#
+# All requests have a "dispatch" field indicating what to do, and a "uuid"
+# field which should be unique for each test, to avoid tests that fail to
+# clean up after themselves, or that are running concurrently, from interfering
+# with other tests.
+#
+# Each uuid has a stash entry with a dictionary with two entries:
+# "trackedRequests" is a list of all observed requested URLs with a
+# dispatch of "track_get" or "track_post". POSTS are in the format
+# "<url>, body: <body>".
+# "errors" is a list of an errors that occurred.
+#
+# A dispatch of "request_list" will return the "trackedRequests" dictionary
+# associated with the in uuid, as a JSON string.
+#
+# A dispatch of "clean_up" will delete all information associated with the uuid.
+def main(request, response):
+ # Don't cache responses, since tests expect duplicate requests to always
+ # reach the server.
+ response.headers.set(b"Cache-Control", b"no-store")
+
+ dispatch = request.GET.first(b"dispatch", None)
+ uuid = request.GET.first(b"uuid", None)
+
+ if not uuid or not dispatch:
+ return simple_response(request, response, 404, b"Not found",
+ b"Invalid query parameters")
+
+ stash = request.server.stash
+ with stash.lock:
+ # Take ownership of stashed entry, if any. This removes the entry of the
+ # stash.
+ server_state = stash.take(uuid) or {"trackedRequests": [], "errors": []}
+
+ # Clear the entire stash. No work to do, since stash entry was already
+ # removed.
+ if dispatch == b"clean_up":
+ return simple_response(request, response, 200, b"OK",
+ b"cleanup complete")
+
+ # Return the list of entries in the stash. Need to add data back to the
+ # stash first.
+ if dispatch == b"request_list":
+ stash.put(uuid, server_state)
+ return simple_response(request, response, 200, b"OK",
+ json.dumps(server_state))
+
+ # Tracks a request that's expected to be a GET.
+ if dispatch == b"track_get":
+ if request.method != "GET":
+ server_state["errors"].append(
+ request.url + " has wrong method: " + request.method)
+ else:
+ server_state["trackedRequests"].append(request.url)
+
+ stash.put(uuid, server_state)
+ return simple_response(request, response, 200, b"OK", b"")
+
+ # Tracks a request that's expected to be a POST.
+ # In addition to the method, check the Content-Type, which is currently
+ # always text/plain, and compare the body against the expected body.
+ if dispatch == b"track_post":
+ contentType = request.headers.get(b"Content-Type", b"missing")
+ if request.method != "POST":
+ server_state["errors"].append(
+ request.url + " has wrong method: " + request.method)
+ elif not contentType.startswith(b"text/plain"):
+ server_state["errors"].append(
+ request.url + " has wrong Content-Type: " +
+ contentType.decode("utf-8"))
+ else:
+ server_state["trackedRequests"].append(
+ request.url + ", body: " + request.body.decode("utf-8"))
+ stash.put(uuid, server_state)
+ return simple_response(request, response, 200, b"OK", b"")
+
+ # Report unrecognized dispatch line.
+ server_state["errors"].append(
+ request.url + " request with unknown dispatch value received: " +
+ dispatch.decode("utf-8"))
+ stash.put(uuid, server_state)
+ return simple_response(request, response, 404, b"Not Found",
+ b"Unrecognized dispatch parameter: " + dispatch)
+
+def simple_response(request, response, status_code, status_message, body,
+ content_type=b"text/plain"):
+ response.status = (status_code, status_message)
+ response.headers.set(b"Content-Type", content_type)
+ return body
diff --git a/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py b/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py
new file mode 100644
index 0000000000..cdd7052a96
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py
@@ -0,0 +1,124 @@
+import json
+from urllib.parse import unquote_plus
+
+# Script to generate trusted bidding signals. The responses depends on the
+# keys and interestGroupNames - some result in entire response failures, others
+# affect only their own value. Keys are preferentially used over
+# interestGroupName, since keys are composible, but some tests need to cover
+# there being no keys.
+def main(request, response):
+ hostname = None
+ keys = None
+ interestGroupNames = None
+
+ # Manually parse query params. Can't use request.GET because it unescapes as well as splitting,
+ # and commas mean very different things from escaped commas.
+ for param in request.url_parts.query.split("&"):
+ pair = param.split("=", 1)
+ if len(pair) != 2:
+ return fail(response, "Bad query parameter: " + param)
+ # Browsers should escape query params consistently.
+ if "%20" in pair[1]:
+ return fail(response, "Query parameter should escape using '+': " + param)
+
+ # Hostname can't be empty. The empty string can be a key or interest group name, though.
+ if pair[0] == "hostname" and hostname == None and len(pair[1]) > 0:
+ hostname = pair[1]
+ continue
+ if pair[0] == "keys" and keys == None:
+ keys = list(map(unquote_plus, pair[1].split(",")))
+ continue
+ if pair[0] == "interestGroupNames" and interestGroupNames == None:
+ interestGroupNames = list(map(unquote_plus, pair[1].split(",")))
+ continue
+ return fail(response, "Unexpected query parameter: " + param)
+
+ # "interestGroupNames" and "hostname" are mandatory.
+ if not hostname:
+ return fail(response, "hostname missing")
+ if not interestGroupNames:
+ return fail(response, "interestGroupNames missing")
+
+ response.status = (200, b"OK")
+
+ # The JSON representation of this is used as the response body. This does
+ # not currently include a "perInterestGroupData" object.
+ responseBody = {"keys": {}}
+
+ # Set when certain special keys are observed, used in place of the JSON
+ # representation of `responseBody`, when set.
+ body = None
+
+ contentType = "application/json"
+ xAllowFledge = "true"
+ dataVersion = None
+ if keys:
+ for key in keys:
+ value = "default value"
+ if key == "close-connection":
+ # Close connection without writing anything, to simulate a
+ # network error. The write call is needed to avoid writing the
+ # default headers.
+ response.writer.write("")
+ response.close_connection = True
+ return
+ elif key.startswith("replace-body:"):
+ # Replace entire response body. Continue to run through other
+ # keys, to allow them to modify request headers.
+ body = key.split(':', 1)[1]
+ elif key.startswith("data-version:"):
+ dataVersion = key.split(':', 1)[1]
+ elif key == "http-error":
+ response.status = (404, b"Not found")
+ elif key == "no-content-type":
+ contentType = None
+ elif key == "wrong-content-type":
+ contentType = 'text/plain'
+ elif key == "wrongContentType":
+ contentType = 'text/plain'
+ elif key == "bad-allow-fledge":
+ xAllowFledge = "sometimes"
+ elif key == "fledge-not-allowed":
+ xAllowFledge = "false"
+ elif key == "no-allow-fledge":
+ xAllowFledge = None
+ elif key == "no-value":
+ continue
+ elif key == "wrong-value":
+ responseBody["keys"]["another-value"] = "another-value"
+ continue
+ elif key == "null-value":
+ value = None
+ elif key == "num-value":
+ value = 1
+ elif key == "string-value":
+ value = "1"
+ elif key == "array-value":
+ value = [1, "foo", None]
+ elif key == "object-value":
+ value = {"a":"b", "c":["d"]}
+ elif key == "interest-group-names":
+ value = json.dumps(interestGroupNames)
+ elif key == "hostname":
+ value = request.GET.first(b"hostname", b"not-found").decode("ASCII")
+ responseBody["keys"][key] = value
+
+ if "data-version" in interestGroupNames:
+ dataVersion = "4"
+
+ if contentType:
+ response.headers.set("Content-Type", contentType)
+ if xAllowFledge:
+ response.headers.set("X-Allow-FLEDGE", xAllowFledge)
+ if dataVersion:
+ response.headers.set("Data-Version", dataVersion)
+ response.headers.set("X-fledge-bidding-signals-format-version", "2")
+
+ if body != None:
+ return body
+ return json.dumps(responseBody)
+
+def fail(response, body):
+ response.status = (400, "Bad Request")
+ response.headers.set(b"Content-Type", b"text/plain")
+ return body
diff --git a/testing/web-platform/tests/fledge/tentative/send-report-to.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/send-report-to.https.sub.window.js
new file mode 100644
index 0000000000..de22e827a3
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/send-report-to.https.sub.window.js
@@ -0,0 +1,163 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict;"
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ 'sellerSignals === null',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ [createSellerReportUrl(uuid), createBidderReportUrl(uuid)]
+ );
+}, 'Both send reports, seller passes nothing to bidder.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ null,
+ '',
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid)]
+ );
+}, 'Only seller sends a report');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ null,
+ 'throw new Error("Very serious exception")',
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid)]
+ );
+}, 'Only seller sends a report, bidder throws an exception');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ null,
+ null,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid)]
+ );
+}, 'Only seller sends a report, bidder has no reportWin() method');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ '',
+ // reportWin:
+ 'sellerSignals === null',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createBidderReportUrl(uuid)]
+ );
+}, 'Only bidder sends a report');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ 'return "foo";',
+ // reportWin:
+ 'sellerSignals === "foo"',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createBidderReportUrl(uuid)]
+ );
+}, 'Only bidder sends a report, seller passes a message to bidder');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ 'throw new Error("Very serious exception")',
+ // reportWin:
+ 'sellerSignals === null',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createBidderReportUrl(uuid)]
+ );
+}, 'Only bidder sends a report, seller throws an exception');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ null,
+ // reportWin:
+ 'sellerSignals === null',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createBidderReportUrl(uuid)]
+ );
+}, 'Only bidder sends a report, seller has no reportResult() method');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');
+ sendReportTo('${createSellerReportUrl(uuid)}');
+ return 5;`,
+ // reportWin:
+ 'sellerSignals === null',
+ `sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createBidderReportUrl(uuid)]
+ );
+}, 'Seller calls sendReportTo() twice, which throws an exception.');
+
+promise_test(async test => {
+ const uuid = generateUuid(test);
+ await runReportTest(
+ test, uuid,
+ // reportResult:
+ null,
+ `sendReportTo('${createSellerReportUrl(uuid)}');`,
+ // reportWin:
+ null,
+ `sendReportTo('${createBidderReportUrl(uuid)}');
+ sendReportTo('${createBidderReportUrl(uuid)}');`,
+ // expectedReportUrls:
+ [createSellerReportUrl(uuid)]
+ );
+ // Seller reports may be sent before bidder reports, since reportWin()
+ // takes output from reportResult() as input. Wait to make sure the
+ // bidder report URL really is not being requested.
+ await new Promise(resolve => test.step_timeout(resolve, 200));
+ await waitForObservedRequests(uuid, [createSellerReportUrl(uuid)]);
+}, 'Bidder calls sendReportTo() twice, which throws an exception.');
diff --git a/testing/web-platform/tests/fledge/tentative/trusted-bidding-signals.https.sub.window.js b/testing/web-platform/tests/fledge/tentative/trusted-bidding-signals.https.sub.window.js
new file mode 100644
index 0000000000..0f8b239639
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/trusted-bidding-signals.https.sub.window.js
@@ -0,0 +1,431 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict";
+
+// These tests focus on trustedBiddingSignals: Requesting them, handling network
+// errors, handling the keys portion of the response, and passing keys to
+// worklet scripts, and handling the Data-Version header
+//
+// Because of request batching, left over interest groups from
+// other tests may result in tests that request TRUSTED_BIDDING_SIGNALS_URL
+// with certain special keys failing, if interest groups with names other than
+// the default one are not successfully left after previously run tests.
+
+// Helper for trusted bidding signals test. Runs an auction, and fails the
+// test if there's no winner. "generateBidCheck" is an expression that should
+// be true when evaluated in generateBid(). "interestGroupOverrides" is a
+// set of overridden fields added to the default interestGroup when joining it,
+// allowing trusted bidding signals keys and URL to be set, in addition to other
+// fields.
+async function runTrustedBiddingSignalsTest(
+ test, generateBidCheck, interestGroupOverrides = {}) {
+ interestGroupOverrides.biddingLogicUrl =
+ createBiddingScriptUrl({
+ generateBid: `if (!(${generateBidCheck})) return false;` });
+ await runBasicFledgeTestExpectingWinner(
+ test, {interestGroupOverrides: interestGroupOverrides});
+}
+
+// Much like runTrustedBiddingSignalsTest, but runs auctions through reporting
+// as well, and evaluates `check` both in generateBid() and reportWin(). Also
+// makes sure browserSignals.dataVersion is undefined in scoreAd() and
+// reportResult().
+async function runTrustedBiddingSignalsDataVersionTest(
+ test, check, interestGroupOverrides = {}) {
+ const uuid = generateUuid(test);
+ interestGroupOverrides.biddingLogicUrl =
+ createBiddingScriptUrl({
+ generateBid:
+ `if (!(${check})) return false;`,
+ reportWin:
+ `if (!(${check}))
+ sendReportTo('${createBidderReportUrl(uuid, 'error')}');
+ else
+ sendReportTo('${createBidderReportUrl(uuid)}');` });
+ await joinInterestGroup(test, uuid, interestGroupOverrides);
+
+ const auctionConfigOverrides = {
+ decisionLogicUrl: createDecisionScriptUrl(
+ uuid,
+ { scoreAd:
+ `if (browserSignals.dataVersion !== undefined)
+ return false;`,
+ reportResult:
+ `if (browserSignals.dataVersion !== undefined)
+ sendReportTo('${createSellerReportUrl(uuid, 'error')}')
+ sendReportTo('${createSellerReportUrl(uuid)}')`,
+ })
+ }
+ await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides);
+ await waitForObservedRequests(
+ uuid, [createBidderReportUrl(uuid), createSellerReportUrl(uuid)]);
+}
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(test, 'trustedBiddingSignals === null');
+}, 'No trustedBiddingSignalsKeys or trustedBiddingSignalsUrl.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['numValue'] });
+}, 'trustedBiddingSignalsKeys but no trustedBiddingSignalsUrl.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'trustedBiddingSignalsUrl without trustedBiddingSignalsKeys.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['close-connection'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'trustedBiddingSignalsUrl closes the connection without sending anything.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['http-error'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response is HTTP 404 error.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['no-content-type'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no content-type.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['wrong-content-type'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has wrong content-type.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['fledge-not-allowed'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response does not allow fledge.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['bad-allow-fledge'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has wrong X-Allow-FLEDGE header.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['no-allow-fledge'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no X-Allow-FLEDGE header.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['replace-body:'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no body.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['replace-body:Not JSON'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response is not JSON.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['replace-body:[]'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response is a JSON array.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals === null',
+ { trustedBiddingSignalsKeys: ['replace-body:{JSON_keys_need_quotes: 1}'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response in invalid JSON object.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals["replace-body:{}"] === null',
+ { trustedBiddingSignalsKeys: ['replace-body:{}'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no keys object.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, `trustedBiddingSignals['replace-body:{"keys":{}}'] === null`,
+ { trustedBiddingSignalsKeys: ['replace-body:{"keys":{}}'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no keys.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `trustedBiddingSignals["0"] === null &&
+ trustedBiddingSignals["1"] === null &&
+ trustedBiddingSignals["2"] === null &&
+ trustedBiddingSignals["length"] === null`,
+ { trustedBiddingSignalsKeys:
+ ['replace-body:{"keys":[1,2,3]}', "0", "1", "2", "length"],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response keys is incorrectly an array.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `trustedBiddingSignals["wrong-value"] === null &&
+ trustedBiddingSignals["another-value"] === undefined`,
+ { trustedBiddingSignalsKeys: ['wrong-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has key not in trustedBiddingSignalsKeys.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals["null-value"] === null',
+ { trustedBiddingSignalsKeys: ['null-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response null value for key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals["num-value"] === 1',
+ { trustedBiddingSignalsKeys: ['num-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has a number value for key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test, 'trustedBiddingSignals["string-value"] === "1"',
+ { trustedBiddingSignalsKeys: ['string-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has string value for key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `JSON.stringify(trustedBiddingSignals["array-value"]) === '[1,"foo",null]'`,
+ { trustedBiddingSignalsKeys: ['array-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has array value for key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `Object.keys(trustedBiddingSignals["object-value"]).length === 2 &&
+ trustedBiddingSignals["object-value"]["a"] === "b" &&
+ JSON.stringify(trustedBiddingSignals["object-value"]["c"]) === '["d"]'`,
+ { trustedBiddingSignalsKeys: ['object-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has object value for key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ 'trustedBiddingSignals[""] === "default value"',
+ { trustedBiddingSignalsKeys: [''],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives empty string key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `Object.keys(trustedBiddingSignals).length === 6 &&
+ trustedBiddingSignals["wrong-value"] === null &&
+ trustedBiddingSignals["null-value"] === null &&
+ trustedBiddingSignals["num-value"] === 1 &&
+ trustedBiddingSignals["string-value"] === "1" &&
+ JSON.stringify(trustedBiddingSignals["array-value"]) === '[1,"foo",null]' &&
+ trustedBiddingSignals[""] === "default value"`,
+ { trustedBiddingSignalsKeys: ['wrong-value', 'null-value', 'num-value',
+ 'string-value', 'array-value', ''],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has multiple keys.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ 'trustedBiddingSignals["+%20 \x00?,3#&"] === "default value"',
+ { trustedBiddingSignalsKeys: ['+%20 \x00?,3#&'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives escaped key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ 'trustedBiddingSignals["\x00"] === "default value"',
+ { trustedBiddingSignalsKeys: ['\x00'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives null key.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `trustedBiddingSignals["interest-group-names"] === '["${DEFAULT_INTEREST_GROUP_NAME}"]'`,
+ { trustedBiddingSignalsKeys: ['interest-group-names'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives interest group name.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ // Interest group names is a JSONified list of JSONified names, so the
+ // null ends up being escaped twice.
+ `trustedBiddingSignals["interest-group-names"] === '["+%20 \\\\u0000?,3#&"]'`,
+ { name: '+%20 \x00?,3#&',
+ trustedBiddingSignalsKeys: ['interest-group-names'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives escaped interest group name.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `trustedBiddingSignals["interest-group-names"] === '[""]'`,
+ { name: '',
+ trustedBiddingSignalsKeys: ['interest-group-names'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives empty interest group name.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsTest(
+ test,
+ `trustedBiddingSignals["hostname"] === "${window.location.hostname}"`,
+ { trustedBiddingSignalsKeys: ['hostname'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals receives hostname field.');
+
+/////////////////////////////////////////////////////////////////////////////
+// Data-Version tests
+/////////////////////////////////////////////////////////////////////////////
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['num-value'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has no data-version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === 3',
+ { trustedBiddingSignalsKeys: ['data-version:3'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has numeric Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === 0',
+ { trustedBiddingSignalsKeys: ['data-version:0'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has min Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === 4294967295',
+ { trustedBiddingSignalsKeys: ['data-version:4294967295'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has max Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:4294967296'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has too large Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:03'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has Data-Version with leading 0.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:-1'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has negative Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:1.3'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has decimal in Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:2 2'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has space in Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:0x4'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has hex Data-Version.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === 4',
+ { name: 'data-version',
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response has Data-Version and no trustedBiddingSignalsKeys.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:3', 'replace-body:'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response with Data-Version and empty body.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:3', 'replace-body:[]'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response with Data-Version and JSON array body.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === undefined',
+ { trustedBiddingSignalsKeys: ['data-version:3', 'replace-body:{} {}'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response with Data-Version and double JSON object body.');
+
+promise_test(async test => {
+ await runTrustedBiddingSignalsDataVersionTest(
+ test,
+ 'browserSignals.dataVersion === 3',
+ { trustedBiddingSignalsKeys: ['data-version:3', 'replace-body:{"keys":5}'],
+ trustedBiddingSignalsUrl: TRUSTED_BIDDING_SIGNALS_URL });
+}, 'Trusted bidding signals response with Data-Version and invalid keys entry');