| // META: script=/resources/testdriver.js |
| // META: script=/resources/testdriver-vendor.js |
| // META: script=/common/utils.js |
| // META: script=resources/fledge-util.sub.js |
| // META: script=third_party/cbor-js/cbor.js |
| // META: script=/common/subset-tests.js |
| // META: timeout=long |
| // META: variant=?1-5 |
| // META: variant=?6-last |
| |
| 'use strict'; |
| |
| // The tests in this file focus on real time reporting. |
| |
| const MAIN_PATH = '/.well-known/interest-group/real-time-report' |
| |
| // Creates an AuctionConfig with component auctions. |
| function createMultiSellerAuctionConfig( |
| uuid, seller, decisionLogicURL, componentAuctions, |
| auctionConfigOverrides = {}) { |
| return { |
| seller: seller, |
| decisionLogicURL: decisionLogicURL, |
| interestGroupBuyers: [], |
| componentAuctions: componentAuctions, |
| ...auctionConfigOverrides |
| }; |
| } |
| |
| // Creates a bidding script located on "origin". The generateBid() method calls |
| // real time reporting API and returns a bid of "bid". |
| // The reportWin() method is empty. |
| function createBiddingScriptURLForRealTimeReporting(origin = null, bid = 1) { |
| return createBiddingScriptURL({ |
| origin: origin ? origin : new URL(BASE_URL).origin, |
| bid: bid, |
| allowComponentAuction: true, |
| generateBid: ` |
| realTimeReporting.contributeToHistogram({ bucket: 20, priorityWeight: 1});` |
| }); |
| } |
| |
| // Creates a decision script that calls real time reporting API. |
| // The reportResult() method is empty. |
| function createDecisionScriptURLForRealTimeReporting(uuid, origin = null) { |
| return createDecisionScriptURL(uuid, { |
| origin: origin === null ? new URL(BASE_URL).origin : origin, |
| scoreAd: ` |
| realTimeReporting.contributeToHistogram({ bucket: 200, priorityWeight: 1});` |
| }); |
| } |
| |
| // Delay method that waits for prescribed number of milliseconds. |
| const delay = ms => new Promise(resolve => step_timeout(resolve, ms)); |
| |
| // Polls the given `origin` to retrieve reports sent there. Once the reports are |
| // received, returns the list of reports. Returns null if the timeout is reached |
| // before a report is available. |
| const pollReports = async (origin, wait_for = 1, timeout = 5000 /*ms*/) => { |
| let startTime = performance.now(); |
| let payloads = []; |
| while (performance.now() - startTime < timeout) { |
| const resp = await fetch(new URL(MAIN_PATH, origin)); |
| const payload = await resp.arrayBuffer(); |
| if (payload.byteLength > 0) { |
| payloads = payloads.concat(payload); |
| } |
| if (payloads.length >= wait_for) { |
| return payloads; |
| } |
| await delay(/*ms=*/ 100); |
| } |
| if (payloads.length > 0) { |
| return payloads; |
| } |
| return null; |
| }; |
| |
| // Verifies that `reports` has 1 report in cbor, which has the expected three |
| // fields. |
| // `version` should be 1. |
| // `histogram` and `platformHistogram` should be objects that pass |
| // verifyHistogram(). |
| const verifyReports = (reports) => { |
| assert_equals(reports.length, 1); |
| const report = CBOR.decode(reports[0]); |
| assert_own_property(report, 'version'); |
| assert_equals(report.version, 1); |
| assert_own_property(report, 'histogram'); |
| verifyHistogram(report.histogram, 128, 1024); |
| assert_own_property(report, 'platformHistogram'); |
| verifyHistogram(report.platformHistogram, 1, 4); |
| assert_equals(Object.keys(report).length, 3); |
| }; |
| |
| // Verifies that a `histogram` has two fields: "buckets" and "length", where |
| // "buckets" field is a Uint8Array of `bucketSize`, and "length" field equals to |
| // `length`. |
| const verifyHistogram = (histogram, bucketSize, length) => { |
| assert_own_property(histogram, 'buckets'); |
| assert_own_property(histogram, 'length'); |
| assert_equals(Object.keys(histogram).length, 2); |
| assert_true(histogram.buckets instanceof Uint8Array); |
| assert_equals(histogram.buckets.length, bucketSize); |
| assert_equals(histogram.length, length); |
| }; |
| |
| // Method to clear the stash. Takes the URL as parameter. |
| const resetReports = url => { |
| // The view of the stash is path-specific |
| // (https://web-platform-tests.org/tools/wptserve/docs/stash.html), therefore |
| // the origin doesn't need to be specified. |
| url = `${url}?clear_stash=true`; |
| const options = { |
| method: 'POST', |
| }; |
| return fetch(url, options); |
| }; |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1], |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, |
| perBuyerRealTimeReportingConfig: |
| {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} |
| }); |
| const sellerReports = await pollReports(location.origin); |
| verifyReports(sellerReports); |
| |
| const buyerReports = await pollReports(OTHER_ORIGIN1); |
| verifyReports(buyerReports); |
| }, 'Real time reporting different buyer and seller both opted-in and called api.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1], |
| perBuyerRealTimeReportingConfig: |
| {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} |
| }); |
| |
| const buyerReports = await pollReports(OTHER_ORIGIN1); |
| verifyReports(buyerReports); |
| |
| // Seller called the RTR API, but didn't opt-in. |
| const sellerReports = |
| await pollReports(location.origin, /*wait_for=*/ 1, /*timeout=*/ 1000); |
| assert_equals(sellerReports, null); |
| }, 'Real time reporting buyer opted-in but not seller.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1], |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'} |
| }); |
| |
| const sellerReports = await pollReports(location.origin); |
| verifyReports(sellerReports); |
| |
| // Buyer called the RTR API, but didn't opt-in. |
| const buyerReports = |
| await pollReports(OTHER_ORIGIN1, /*wait_for=*/ 1, /*timeout=*/ 1000); |
| assert_equals(buyerReports, null); |
| }, 'Real time reporting seller opted-in but not buyer.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup( |
| test, uuid, OTHER_ORIGIN1, |
| {biddingLogicURL: createBiddingScriptURL({origin: OTHER_ORIGIN1})}); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURL(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1], |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, |
| perBuyerRealTimeReportingConfig: |
| {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} |
| }); |
| const sellerReports = await pollReports(location.origin); |
| verifyReports(sellerReports); |
| |
| const buyerReports = await pollReports(OTHER_ORIGIN1); |
| verifyReports(buyerReports); |
| }, 'Real time reporting different buyer and seller both opted-in but did not call api.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1] |
| }); |
| const sellerReports = await pollReports(location.origin); |
| assert_equals(sellerReports, null); |
| const buyerReports = |
| await pollReports(OTHER_ORIGIN1, /*wait_for=*/ 1, /*timeout=*/ 1000); |
| assert_equals(buyerReports, null); |
| }, 'Real time reporting both called api but did not opt-in.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinInterestGroup( |
| test, uuid, |
| {biddingLogicURL: createBiddingScriptURLForRealTimeReporting()}); |
| |
| const origin = location.origin; |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, |
| perBuyerRealTimeReportingConfig: |
| {[origin]: {type: 'default-local-reporting'}} |
| }); |
| const reports = await pollReports(origin); |
| verifyReports(reports); |
| }, 'Real time reporting buyer and seller same origin.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN2, { |
| biddingLogicURL: |
| createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN2, /*bid=*/ 100) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1, OTHER_ORIGIN2], |
| perBuyerRealTimeReportingConfig: { |
| [OTHER_ORIGIN1]: {type: 'default-local-reporting'}, |
| [OTHER_ORIGIN2]: {type: 'default-local-reporting'} |
| } |
| }); |
| const reports1 = await pollReports(OTHER_ORIGIN1); |
| verifyReports(reports1); |
| |
| const reports2 = await pollReports(OTHER_ORIGIN2); |
| verifyReports(reports2); |
| }, 'Real time reporting both winning and losing buyers opted-in.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN2, { |
| biddingLogicURL: |
| createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN2, /*bid=*/ 100) |
| }); |
| await runBasicFledgeAuctionAndNavigate(test, uuid, { |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), |
| interestGroupBuyers: [OTHER_ORIGIN1, OTHER_ORIGIN2], |
| perBuyerRealTimeReportingConfig: |
| {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} |
| }); |
| const reports1 = await pollReports(OTHER_ORIGIN1); |
| verifyReports(reports1); |
| |
| const reports2 = |
| await pollReports(OTHER_ORIGIN2, /*wait_for=*/ 1, /*timeout=*/ 1000); |
| assert_equals(reports2, null); |
| }, 'Real time reporting one buyer opted-in but not the other.'); |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { |
| biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) |
| }); |
| await runBasicFledgeTestExpectingNoWinner(test, uuid, { |
| decisionLogicURL: createDecisionScriptURL(uuid, { |
| scoreAd: ` |
| realTimeReporting.contributeToHistogram({ bucket: 200, priorityWeight: 1}); |
| return -1;` |
| }), |
| interestGroupBuyers: [OTHER_ORIGIN1], |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, |
| perBuyerRealTimeReportingConfig: |
| {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} |
| }); |
| const sellerReports = await pollReports(location.origin); |
| verifyReports(sellerReports); |
| |
| const buyerReports = await pollReports(OTHER_ORIGIN1); |
| verifyReports(buyerReports); |
| }, 'Real time reports are sent when all bids are rejected.'); |
| |
| // TODO(qingxinwu): script fetches failing cases. |
| |
| subsetTest(promise_test, async test => { |
| const uuid = generateUuid(test); |
| |
| let buyer = window.location.origin; |
| let componentSellerOptIn = OTHER_ORIGIN1; |
| let componentSellerNotOptIn = OTHER_ORIGIN2; |
| let topLevelSeller = OTHER_ORIGIN3; |
| await resetReports(MAIN_PATH); |
| await joinCrossOriginInterestGroup( |
| test, uuid, buyer, |
| {biddingLogicURL: createBiddingScriptURLForRealTimeReporting(buyer)}); |
| |
| const componentAuctions = [ |
| { |
| seller: componentSellerOptIn, |
| interestGroupBuyers: [buyer], |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting( |
| uuid, componentSellerOptIn), |
| sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, |
| perBuyerRealTimeReportingConfig: |
| {[buyer]: {type: 'default-local-reporting'}} |
| }, |
| { |
| seller: componentSellerNotOptIn, |
| interestGroupBuyers: [buyer], |
| decisionLogicURL: createDecisionScriptURLForRealTimeReporting( |
| uuid, componentSellerNotOptIn), |
| } |
| ]; |
| let auctionConfig = createMultiSellerAuctionConfig( |
| uuid, topLevelSeller, |
| createDecisionScriptURLForRealTimeReporting(uuid, topLevelSeller), |
| componentAuctions, {}); |
| auctionConfig.sellerRealTimeReportingConfig = { |
| type: 'default-local-reporting' |
| }; |
| |
| await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfig); |
| const reportsBuyer = await pollReports(buyer); |
| verifyReports(reportsBuyer); |
| |
| const reportsComponentSellerOptIn = await pollReports(componentSellerOptIn); |
| verifyReports(reportsComponentSellerOptIn); |
| |
| const reportsTopLevelSeller = await pollReports(topLevelSeller); |
| verifyReports(reportsTopLevelSeller); |
| |
| const reportsComponentSellerNotOptIn = await pollReports( |
| componentSellerOptIn, /*wait_for=*/ 1, /*timeout=*/ 1000); |
| assert_equals(reportsComponentSellerNotOptIn, null); |
| }, 'Real time reporting in a multi seller auction.'); |