real-time-reporting.https.window.js (13925B)
1 // META: script=/resources/testdriver.js 2 // META: script=/resources/testdriver-vendor.js 3 // META: script=/common/utils.js 4 // META: script=resources/fledge-util.sub.js 5 // META: script=third_party/cbor-js/cbor.js 6 // META: script=/common/subset-tests.js 7 // META: timeout=long 8 // META: variant=?1-5 9 // META: variant=?6-last 10 11 'use strict'; 12 13 // The tests in this file focus on real time reporting. 14 15 const MAIN_PATH = '/.well-known/interest-group/real-time-report' 16 17 // Creates an AuctionConfig with component auctions. 18 function createMultiSellerAuctionConfig( 19 uuid, seller, decisionLogicURL, componentAuctions, 20 auctionConfigOverrides = {}) { 21 return { 22 seller: seller, 23 decisionLogicURL: decisionLogicURL, 24 interestGroupBuyers: [], 25 componentAuctions: componentAuctions, 26 ...auctionConfigOverrides 27 }; 28 } 29 30 // Creates a bidding script located on "origin". The generateBid() method calls 31 // real time reporting API and returns a bid of "bid". 32 // The reportWin() method is empty. 33 function createBiddingScriptURLForRealTimeReporting(origin = null, bid = 1) { 34 return createBiddingScriptURL({ 35 origin: origin ? origin : new URL(BASE_URL).origin, 36 bid: bid, 37 allowComponentAuction: true, 38 generateBid: ` 39 realTimeReporting.contributeToHistogram({ bucket: 20, priorityWeight: 1});` 40 }); 41 } 42 43 // Creates a decision script that calls real time reporting API. 44 // The reportResult() method is empty. 45 function createDecisionScriptURLForRealTimeReporting(uuid, origin = null) { 46 return createDecisionScriptURL(uuid, { 47 origin: origin === null ? new URL(BASE_URL).origin : origin, 48 scoreAd: ` 49 realTimeReporting.contributeToHistogram({ bucket: 200, priorityWeight: 1});` 50 }); 51 } 52 53 // Delay method that waits for prescribed number of milliseconds. 54 const delay = ms => new Promise(resolve => step_timeout(resolve, ms)); 55 56 // Polls the given `origin` to retrieve reports sent there. Once the reports are 57 // received, returns the list of reports. Returns null if the timeout is reached 58 // before a report is available. 59 const pollReports = async (origin, wait_for = 1, timeout = 5000 /*ms*/) => { 60 let startTime = performance.now(); 61 let payloads = []; 62 while (performance.now() - startTime < timeout) { 63 const resp = await fetch(new URL(MAIN_PATH, origin)); 64 const payload = await resp.arrayBuffer(); 65 if (payload.byteLength > 0) { 66 payloads = payloads.concat(payload); 67 } 68 if (payloads.length >= wait_for) { 69 return payloads; 70 } 71 await delay(/*ms=*/ 100); 72 } 73 if (payloads.length > 0) { 74 return payloads; 75 } 76 return null; 77 }; 78 79 // Verifies that `reports` has 1 report in cbor, which has the expected three 80 // fields. 81 // `version` should be 1. 82 // `histogram` and `platformHistogram` should be objects that pass 83 // verifyHistogram(). 84 const verifyReports = (reports) => { 85 assert_equals(reports.length, 1); 86 const report = CBOR.decode(reports[0]); 87 assert_own_property(report, 'version'); 88 assert_equals(report.version, 1); 89 assert_own_property(report, 'histogram'); 90 verifyHistogram(report.histogram, 128, 1024); 91 assert_own_property(report, 'platformHistogram'); 92 verifyHistogram(report.platformHistogram, 1, 4); 93 assert_equals(Object.keys(report).length, 3); 94 }; 95 96 // Verifies that a `histogram` has two fields: "buckets" and "length", where 97 // "buckets" field is a Uint8Array of `bucketSize`, and "length" field equals to 98 // `length`. 99 const verifyHistogram = (histogram, bucketSize, length) => { 100 assert_own_property(histogram, 'buckets'); 101 assert_own_property(histogram, 'length'); 102 assert_equals(Object.keys(histogram).length, 2); 103 assert_true(histogram.buckets instanceof Uint8Array); 104 assert_equals(histogram.buckets.length, bucketSize); 105 assert_equals(histogram.length, length); 106 }; 107 108 // Method to clear the stash. Takes the URL as parameter. 109 const resetReports = url => { 110 // The view of the stash is path-specific 111 // (https://web-platform-tests.org/tools/wptserve/docs/stash.html), therefore 112 // the origin doesn't need to be specified. 113 url = `${url}?clear_stash=true`; 114 const options = { 115 method: 'POST', 116 }; 117 return fetch(url, options); 118 }; 119 120 subsetTest(promise_test, async test => { 121 const uuid = generateUuid(test); 122 await resetReports(MAIN_PATH); 123 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 124 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 125 }); 126 await runBasicFledgeAuctionAndNavigate(test, uuid, { 127 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 128 interestGroupBuyers: [OTHER_ORIGIN1], 129 sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, 130 perBuyerRealTimeReportingConfig: 131 {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} 132 }); 133 const sellerReports = await pollReports(location.origin); 134 verifyReports(sellerReports); 135 136 const buyerReports = await pollReports(OTHER_ORIGIN1); 137 verifyReports(buyerReports); 138 }, 'Real time reporting different buyer and seller both opted-in and called api.'); 139 140 subsetTest(promise_test, async test => { 141 const uuid = generateUuid(test); 142 await resetReports(MAIN_PATH); 143 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 144 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 145 }); 146 await runBasicFledgeAuctionAndNavigate(test, uuid, { 147 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 148 interestGroupBuyers: [OTHER_ORIGIN1], 149 perBuyerRealTimeReportingConfig: 150 {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} 151 }); 152 153 const buyerReports = await pollReports(OTHER_ORIGIN1); 154 verifyReports(buyerReports); 155 156 // Seller called the RTR API, but didn't opt-in. 157 const sellerReports = 158 await pollReports(location.origin, /*wait_for=*/ 1, /*timeout=*/ 1000); 159 assert_equals(sellerReports, null); 160 }, 'Real time reporting buyer opted-in but not seller.'); 161 162 subsetTest(promise_test, async test => { 163 const uuid = generateUuid(test); 164 await resetReports(MAIN_PATH); 165 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 166 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 167 }); 168 await runBasicFledgeAuctionAndNavigate(test, uuid, { 169 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 170 interestGroupBuyers: [OTHER_ORIGIN1], 171 sellerRealTimeReportingConfig: {type: 'default-local-reporting'} 172 }); 173 174 const sellerReports = await pollReports(location.origin); 175 verifyReports(sellerReports); 176 177 // Buyer called the RTR API, but didn't opt-in. 178 const buyerReports = 179 await pollReports(OTHER_ORIGIN1, /*wait_for=*/ 1, /*timeout=*/ 1000); 180 assert_equals(buyerReports, null); 181 }, 'Real time reporting seller opted-in but not buyer.'); 182 183 subsetTest(promise_test, async test => { 184 const uuid = generateUuid(test); 185 await resetReports(MAIN_PATH); 186 await joinCrossOriginInterestGroup( 187 test, uuid, OTHER_ORIGIN1, 188 {biddingLogicURL: createBiddingScriptURL({origin: OTHER_ORIGIN1})}); 189 await runBasicFledgeAuctionAndNavigate(test, uuid, { 190 decisionLogicURL: createDecisionScriptURL(uuid), 191 interestGroupBuyers: [OTHER_ORIGIN1], 192 sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, 193 perBuyerRealTimeReportingConfig: 194 {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} 195 }); 196 const sellerReports = await pollReports(location.origin); 197 verifyReports(sellerReports); 198 199 const buyerReports = await pollReports(OTHER_ORIGIN1); 200 verifyReports(buyerReports); 201 }, 'Real time reporting different buyer and seller both opted-in but did not call api.'); 202 203 subsetTest(promise_test, async test => { 204 const uuid = generateUuid(test); 205 await resetReports(MAIN_PATH); 206 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 207 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 208 }); 209 await runBasicFledgeAuctionAndNavigate(test, uuid, { 210 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 211 interestGroupBuyers: [OTHER_ORIGIN1] 212 }); 213 const sellerReports = await pollReports(location.origin); 214 assert_equals(sellerReports, null); 215 const buyerReports = 216 await pollReports(OTHER_ORIGIN1, /*wait_for=*/ 1, /*timeout=*/ 1000); 217 assert_equals(buyerReports, null); 218 }, 'Real time reporting both called api but did not opt-in.'); 219 220 subsetTest(promise_test, async test => { 221 const uuid = generateUuid(test); 222 await resetReports(MAIN_PATH); 223 await joinInterestGroup( 224 test, uuid, 225 {biddingLogicURL: createBiddingScriptURLForRealTimeReporting()}); 226 227 const origin = location.origin; 228 await runBasicFledgeAuctionAndNavigate(test, uuid, { 229 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 230 sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, 231 perBuyerRealTimeReportingConfig: 232 {[origin]: {type: 'default-local-reporting'}} 233 }); 234 const reports = await pollReports(origin); 235 verifyReports(reports); 236 }, 'Real time reporting buyer and seller same origin.'); 237 238 subsetTest(promise_test, async test => { 239 const uuid = generateUuid(test); 240 await resetReports(MAIN_PATH); 241 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 242 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 243 }); 244 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN2, { 245 biddingLogicURL: 246 createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN2, /*bid=*/ 100) 247 }); 248 await runBasicFledgeAuctionAndNavigate(test, uuid, { 249 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 250 interestGroupBuyers: [OTHER_ORIGIN1, OTHER_ORIGIN2], 251 perBuyerRealTimeReportingConfig: { 252 [OTHER_ORIGIN1]: {type: 'default-local-reporting'}, 253 [OTHER_ORIGIN2]: {type: 'default-local-reporting'} 254 } 255 }); 256 const reports1 = await pollReports(OTHER_ORIGIN1); 257 verifyReports(reports1); 258 259 const reports2 = await pollReports(OTHER_ORIGIN2); 260 verifyReports(reports2); 261 }, 'Real time reporting both winning and losing buyers opted-in.'); 262 263 subsetTest(promise_test, async test => { 264 const uuid = generateUuid(test); 265 await resetReports(MAIN_PATH); 266 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 267 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 268 }); 269 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN2, { 270 biddingLogicURL: 271 createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN2, /*bid=*/ 100) 272 }); 273 await runBasicFledgeAuctionAndNavigate(test, uuid, { 274 decisionLogicURL: createDecisionScriptURLForRealTimeReporting(uuid), 275 interestGroupBuyers: [OTHER_ORIGIN1, OTHER_ORIGIN2], 276 perBuyerRealTimeReportingConfig: 277 {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} 278 }); 279 const reports1 = await pollReports(OTHER_ORIGIN1); 280 verifyReports(reports1); 281 282 const reports2 = 283 await pollReports(OTHER_ORIGIN2, /*wait_for=*/ 1, /*timeout=*/ 1000); 284 assert_equals(reports2, null); 285 }, 'Real time reporting one buyer opted-in but not the other.'); 286 287 subsetTest(promise_test, async test => { 288 const uuid = generateUuid(test); 289 await resetReports(MAIN_PATH); 290 await joinCrossOriginInterestGroup(test, uuid, OTHER_ORIGIN1, { 291 biddingLogicURL: createBiddingScriptURLForRealTimeReporting(OTHER_ORIGIN1) 292 }); 293 await runBasicFledgeTestExpectingNoWinner(test, uuid, { 294 decisionLogicURL: createDecisionScriptURL(uuid, { 295 scoreAd: ` 296 realTimeReporting.contributeToHistogram({ bucket: 200, priorityWeight: 1}); 297 return -1;` 298 }), 299 interestGroupBuyers: [OTHER_ORIGIN1], 300 sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, 301 perBuyerRealTimeReportingConfig: 302 {[OTHER_ORIGIN1]: {type: 'default-local-reporting'}} 303 }); 304 const sellerReports = await pollReports(location.origin); 305 verifyReports(sellerReports); 306 307 const buyerReports = await pollReports(OTHER_ORIGIN1); 308 verifyReports(buyerReports); 309 }, 'Real time reports are sent when all bids are rejected.'); 310 311 // TODO(qingxinwu): script fetches failing cases. 312 313 subsetTest(promise_test, async test => { 314 const uuid = generateUuid(test); 315 316 let buyer = window.location.origin; 317 let componentSellerOptIn = OTHER_ORIGIN1; 318 let componentSellerNotOptIn = OTHER_ORIGIN2; 319 let topLevelSeller = OTHER_ORIGIN3; 320 await resetReports(MAIN_PATH); 321 await joinCrossOriginInterestGroup( 322 test, uuid, buyer, 323 {biddingLogicURL: createBiddingScriptURLForRealTimeReporting(buyer)}); 324 325 const componentAuctions = [ 326 { 327 seller: componentSellerOptIn, 328 interestGroupBuyers: [buyer], 329 decisionLogicURL: createDecisionScriptURLForRealTimeReporting( 330 uuid, componentSellerOptIn), 331 sellerRealTimeReportingConfig: {type: 'default-local-reporting'}, 332 perBuyerRealTimeReportingConfig: 333 {[buyer]: {type: 'default-local-reporting'}} 334 }, 335 { 336 seller: componentSellerNotOptIn, 337 interestGroupBuyers: [buyer], 338 decisionLogicURL: createDecisionScriptURLForRealTimeReporting( 339 uuid, componentSellerNotOptIn), 340 } 341 ]; 342 let auctionConfig = createMultiSellerAuctionConfig( 343 uuid, topLevelSeller, 344 createDecisionScriptURLForRealTimeReporting(uuid, topLevelSeller), 345 componentAuctions, {}); 346 auctionConfig.sellerRealTimeReportingConfig = { 347 type: 'default-local-reporting' 348 }; 349 350 await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfig); 351 const reportsBuyer = await pollReports(buyer); 352 verifyReports(reportsBuyer); 353 354 const reportsComponentSellerOptIn = await pollReports(componentSellerOptIn); 355 verifyReports(reportsComponentSellerOptIn); 356 357 const reportsTopLevelSeller = await pollReports(topLevelSeller); 358 verifyReports(reportsTopLevelSeller); 359 360 const reportsComponentSellerNotOptIn = await pollReports( 361 componentSellerOptIn, /*wait_for=*/ 1, /*timeout=*/ 1000); 362 assert_equals(reportsComponentSellerNotOptIn, null); 363 }, 'Real time reporting in a multi seller auction.');