diff options
Diffstat (limited to 'testing/web-platform/tests/fledge/tentative')
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'); |