summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/private-aggregation/resources/protected-audience-helper-module.js
blob: be4f01379e236938c255a366afca97bdc2e79675 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// This file is adapted from /fledge/tentative/resources/fledge-util.js,
// removing unnecessary logic and modifying to allow it to be run in the
// private-aggregation directory.

"use strict;"

const FULL_URL = window.location.href;
let 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';

// Use python script files under fledge directory
const FLEDGE_DIR = '/fledge/tentative/';
const FLEDGE_BASE_URL = BASE_URL.replace(BASE_PATH, FLEDGE_DIR);

// Sleep method that waits for prescribed number of milliseconds.
const sleep = ms => new Promise(resolve => step_timeout(resolve, ms));

// Generates a UUID by token.
function generateUuid() {
  let uuid = token();
  return uuid;
}

// Creates a URL that will be sent to the handler.
// `uuid` is used to identify the stash shard to use.
// `operate` is used to set action as write or read.
// `report` is used to carry the message for write requests.
function createReportingURL(uuid, operation, report = 'default-report') {
  let url = new URL(`${window.location.origin}${BASE_PATH}resources/protected_audience_event_level_report_handler.py`);
  url.searchParams.append('uuid', uuid);
  url.searchParams.append('operation', operation);

  if (report)
    url.searchParams.append('report', report);

  return url.toString();
}

function createWritingURL(uuid, report) {
  return createReportingURL(uuid, 'write');
}

function createReadingURL(uuid) {
  return createReportingURL(uuid, 'read');
}

async function waitForObservedReports(uuid, expectedNumReports, timeout = 5000 /*ms*/) {
  expectedReports = Array(expectedNumReports).fill('default-report');
  const reportURL = createReadingURL(uuid);
  let startTime = performance.now();

  while (performance.now() - startTime < timeout) {
    let response = await fetch(reportURL, { credentials: 'omit', mode: 'cors' });
    let actualReports = await response.json();

    // If expected number of reports have been observed, compare with list of
    // all expected reports and exit.
    if (actualReports.length == expectedReports.length) {
      assert_array_equals(actualReports.sort(), expectedReports);
      return;
    }

    await sleep(/*ms=*/ 100);
  }
  assert_unreached("Report fetching timed out: " + uuid);
}

// 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(`${FLEDGE_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(`${FLEDGE_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(`${FLEDGE_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}".
// `interestGroupOverrides` is required 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,
    ads: [{renderURL: createRenderURL(uuid)}],
    ...interestGroupOverrides
  };

  await navigator.joinAdInterestGroup(interestGroup,
                                      INTEREST_GROUP_LIFETIME_SECS);
  test.add_cleanup(
      async () => {await navigator.leaveAdInterestGroup(interestGroup)});
}

// Runs a FLEDGE auction and returns the result. `auctionConfigOverrides` is
// required to override fields in the auction configuration.
async function runBasicFledgeAuction(test, uuid, auctionConfigOverrides) {
  let auctionConfig = {
    seller: window.location.origin,
    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 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 scoreAd(), reportResult(), generateBid() and reportWin(), as well as
// additional arguments to be passed to joinAdInterestGroup() and runAdAuction()
async function runReportTest(test, uuid, codeToInsert,
                             expectedNumReports = 0, overrides = {}) {
  let generateBid = codeToInsert.generateBid;
  let scoreAd = codeToInsert.scoreAd;
  let reportWin = codeToInsert.reportWin;
  let reportResult = codeToInsert.reportResult;

  let extraInterestGroupOverrides = overrides.joinAdInterestGroup || {}
  let extraAuctionConfigOverrides = overrides.runAdAuction || {}

  let interestGroupOverrides = {
    biddingLogicURL: createBiddingScriptURL({ generateBid, reportWin }),
    ...extraInterestGroupOverrides
  };
  let auctionConfigOverrides = {
    decisionLogicURL: createDecisionScriptURL(
      uuid, { scoreAd, reportResult }),
    ...extraAuctionConfigOverrides
  }

  await joinInterestGroup(test, uuid, interestGroupOverrides);
  await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides);

  if (expectedNumReports) {
    await waitForObservedReports(uuid, expectedNumReports);
  }
}