// META: script=/resources/testdriver.js
// META: script=/common/utils.js
// META: script=/common/subset-tests.js
// META: script=resources/fledge-util.sub.js
// META: timeout=long
// META: variant=?1-4
// META: variant=?5-8
// META: variant=?9-12
// META: variant=?13-last

"use strict;"

////////////////////////////////////////////////////////////////////////////////
// Join interest group in iframe tests.
////////////////////////////////////////////////////////////////////////////////

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  let iframe = await createIframe(test, document.location.origin);

  // Join a same-origin InterestGroup in a iframe navigated to its origin.
  await runInFrame(test, iframe, `await joinInterestGroup(test_instance, "${uuid}");`);

  // Run an auction using window.location.origin as a bidder. The IG should
  // make a bid and win an auction.
  await runBasicFledgeTestExpectingWinner(test, uuid);
}, 'Join interest group in same-origin iframe, default permissions.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  let iframe = await createIframe(test, OTHER_ORIGIN1);

  // Join a cross-origin InterestGroup in a iframe navigated to its origin.
  await runInFrame(test, iframe, `await joinInterestGroup(test_instance, "${uuid}");`);

  // Run an auction in this frame using the other origin as a bidder. The IG should
  // make a bid and win an auction.
  //
  // TODO: Once the permission defaults to not being able to join InterestGroups in
  // cross-origin iframes, this auction should have no winner.
  await runBasicFledgeTestExpectingWinner(
      test, uuid,
      { interestGroupBuyers: [OTHER_ORIGIN1],
        scoreAd: `if (browserSignals.interestGroupOwner !== "${OTHER_ORIGIN1}")
                    throw "Wrong owner: " + browserSignals.interestGroupOwner`
      });
}, 'Join interest group in cross-origin iframe, default permissions.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  let iframe = await createIframe(test, OTHER_ORIGIN1, 'join-ad-interest-group');

  // Join a cross-origin InterestGroup in a iframe navigated to its origin.
  await runInFrame(test, iframe, `await joinInterestGroup(test_instance, "${uuid}");`);

  // Run an auction in this frame using the other origin as a bidder. The IG should
  // make a bid and win an auction.
  await runBasicFledgeTestExpectingWinner(
      test, uuid,
      { interestGroupBuyers: [OTHER_ORIGIN1],
        scoreAd: `if (browserSignals.interestGroupOwner !== "${OTHER_ORIGIN1}")
                    throw "Wrong owner: " + browserSignals.interestGroupOwner`
      });
}, 'Join interest group in cross-origin iframe with join-ad-interest-group permission.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  let iframe = await createIframe(test, OTHER_ORIGIN1, "join-ad-interest-group 'none'");

  // Try to join an InterestGroup in a cross-origin iframe whose permissions policy
  // blocks joining interest groups. An exception should be thrown, and the interest
  // group should not be joined.
  await runInFrame(test, iframe,
                    `try {
                       await joinInterestGroup(test_instance, "${uuid}");
                     } catch (e) {
                       assert_true(e instanceof DOMException, "DOMException thrown");
                       assert_equals(e.name, "NotAllowedError", "NotAllowedError DOMException thrown");
                       return {result: "success"};
                     }
                     return "exception unexpectedly not thrown";`);

  // Run an auction in this frame using the other origin as a bidder. Since the join
  // should have failed, the auction should have no winner.
  await runBasicFledgeTestExpectingNoWinner(
      test, uuid,
      { interestGroupBuyers: [OTHER_ORIGIN1] });
}, 'Join interest group in cross-origin iframe with join-ad-interest-group permission denied.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  let iframe = await createIframe(test, OTHER_ORIGIN1, 'join-ad-interest-group');

  // Try to join an IG with the parent's origin as an owner in a cross-origin iframe.
  // This should require a .well-known fetch to the parents origin, which will not
  // grant permission. The case where permission is granted is not yet testable.
  let interestGroup = JSON.stringify(createInterestGroupForOrigin(uuid, window.location.origin));
  await runInFrame(test, iframe,
                   `try {
                      await joinInterestGroup(test_instance, "${uuid}", ${interestGroup});
                    } catch (e) {
                      assert_true(e instanceof DOMException, "DOMException thrown");
                      assert_equals(e.name, "NotAllowedError", "NotAllowedError DOMException thrown");
                      return {result: "success"};
                    }
                    return "exception unexpectedly not thrown";`);

  // Run an auction with this page's origin as a bidder. Since the join
  // should have failed, the auction should have no winner.
  await runBasicFledgeTestExpectingNoWinner(test, uuid);
}, "Join interest group owned by parent's origin in cross-origin iframe.");

////////////////////////////////////////////////////////////////////////////////
// Run auction in iframe tests.
////////////////////////////////////////////////////////////////////////////////

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  await joinInterestGroup(test, uuid);

  let iframe = await createIframe(test, document.location.origin);

  // Join a same-origin InterestGroup in a iframe navigated to its origin.
  await runInFrame(test, iframe, `await joinInterestGroup(test_instance, "${uuid}");`);

  // Run auction in same-origin iframe. This should succeed, by default.
  await runInFrame(
    test, iframe,
    `await runBasicFledgeTestExpectingWinner(test_instance, "${uuid}");`);
}, 'Run auction in same-origin iframe, default permissions.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  // Join an interest group owned by the the main frame's origin.
  await joinInterestGroup(test, uuid);

  let iframe = await createIframe(test, OTHER_ORIGIN1);

  // Run auction in cross-origin iframe. Currently, this is allowed by default.
  await runInFrame(
      test, iframe,
      `await runBasicFledgeTestExpectingWinner(
           test_instance, "${uuid}",
           {interestGroupBuyers: ["${window.location.origin}"]});`);
}, 'Run auction in cross-origin iframe, default permissions.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  // Join an interest group owned by the the main frame's origin.
  await joinInterestGroup(test, uuid);

  let iframe = await createIframe(test, OTHER_ORIGIN1, "run-ad-auction");

  // Run auction in cross-origin iframe that should allow the auction to occur.
  await runInFrame(
      test, iframe,
      `await runBasicFledgeTestExpectingWinner(
           test_instance, "${uuid}",
           {interestGroupBuyers: ["${window.location.origin}"]});`);
}, 'Run auction in cross-origin iframe with run-ad-auction permission.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  // No need to join any interest groups in this case - running an auction
  // should only throw an exception based on permissions policy, regardless
  // of whether there are any interest groups can participate.

  let iframe = await createIframe(test, OTHER_ORIGIN1, "run-ad-auction 'none'");

  // Run auction in cross-origin iframe that should not allow the auction to occur.
  await runInFrame(
      test, iframe,
      `try {
         await runBasicFledgeAuction(test_instance, "${uuid}");
       } catch (e) {
         assert_true(e instanceof DOMException, "DOMException thrown");
         assert_equals(e.name, "NotAllowedError", "NotAllowedError DOMException thrown");
         return {result: "success"};
       }
       throw "Attempting to run auction unexpectedly did not throw"`);
}, 'Run auction in cross-origin iframe with run-ad-auction permission denied.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  // Join an interest group owned by the the main frame's origin.
  await joinInterestGroup(test, uuid);

  let iframe = await createIframe(test, OTHER_ORIGIN1, `run-ad-auction ${OTHER_ORIGIN1}`);

  await runInFrame(
      test, iframe,
      `await runBasicFledgeTestExpectingWinner(
        test_instance, "${uuid}",
        { interestGroupBuyers: ["${window.location.origin}"],
          seller: "${OTHER_ORIGIN2}",
          decisionLogicURL: createDecisionScriptURL("${uuid}", {origin: "${OTHER_ORIGIN2}"})
        });`);
}, 'Run auction in cross-origin iframe with run-ad-auction for iframe origin, which is different from seller origin.');

////////////////////////////////////////////////////////////////////////////////
// Navigate fenced frame iframe tests.
////////////////////////////////////////////////////////////////////////////////

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  // Join an interest group and run an auction with a winner.
  await joinInterestGroup(test, uuid);
  let config = await runBasicFledgeTestExpectingWinner(test, uuid);

  // Try to navigate a fenced frame to the winning ad in a cross-origin iframe
  // with no fledge-related permissions.
  let iframe = await createIframe(
      test, OTHER_ORIGIN1, "join-ad-interest-group 'none'; run-ad-auction 'none'");
  await runInFrame(
      test, iframe,
      `await createAndNavigateFencedFrame(test_instance, param);`,
      /*param=*/config);
  await waitForObservedRequests(
      uuid, [createBidderReportURL(uuid), createSellerReportURL(uuid)]);
}, 'Run auction main frame, open winning ad in cross-origin iframe.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  let iframe = await createIframe(
      test, OTHER_ORIGIN1, "join-ad-interest-group; run-ad-auction");
  await runInFrame(
      test, iframe,
      `await joinInterestGroup(test_instance, "${uuid}");
       await runBasicFledgeAuctionAndNavigate(test_instance, "${uuid}");
       await waitForObservedRequests(
         "${uuid}", [createBidderReportURL("${uuid}"), createSellerReportURL("${uuid}")])`);
}, 'Run auction in cross-origin iframe and open winning ad in nested fenced frame.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  // Run an auction in an cross-origin iframe, and get the resulting FencedFrameConfig.
  let iframe = await createIframe(
      test, OTHER_ORIGIN1, "join-ad-interest-group; run-ad-auction");
  let config = await runInFrame(
      test, iframe,
      `await joinInterestGroup(test_instance, "${uuid}");
       let config = await runBasicFledgeTestExpectingWinner(test_instance, "${uuid}");
       return {result: "success", returnValue: config};`);
  assert_true(config != null, "Value not returned from auction in iframe");
  assert_true(config instanceof FencedFrameConfig,
    `Wrong value type returned from auction: ${config.constructor.type}`);

  // Loading the winning ad in a fenced frame that's a child of the main frame should
  // succeed.
  await createAndNavigateFencedFrame(test, config);
  await waitForObservedRequests(
      uuid,
      [ createBidderReportURL(uuid, '1', OTHER_ORIGIN1),
        createSellerReportURL(uuid, '1', OTHER_ORIGIN1)]);
}, 'Run auction in cross-origin iframe and open winning ad in a fenced frame child of the main frame.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  // Run an auction in an cross-origin iframe, and get the resulting FencedFrameConfig.
  let iframe = await createIframe(
      test, OTHER_ORIGIN1, "join-ad-interest-group; run-ad-auction");
  let config = await runInFrame(
      test, iframe,
      `await joinInterestGroup(test_instance, "${uuid}");
       let config = await runBasicFledgeTestExpectingWinner(test_instance, "${uuid}");
       return {result: "success", returnValue: config};`);
  assert_true(config != null, "Value not returned from auction in iframe");
  assert_true(config instanceof FencedFrameConfig,
    `Wrong value type returned from auction: ${config.constructor.type}`);

  // Try to navigate a fenced frame to the winning ad in a cross-origin iframe
  // with no fledge-related permissions. The iframe is a different origin from the
  // first cross-origin iframe.
  let iframe2 = await createIframe(
    test, OTHER_ORIGIN2, "join-ad-interest-group 'none'; run-ad-auction 'none'");
  await runInFrame(
      test, iframe2,
      `await createAndNavigateFencedFrame(test_instance, param);`,
      /*param=*/config);
  await waitForObservedRequests(
      uuid,
      [ createBidderReportURL(uuid, '1', OTHER_ORIGIN1),
        createSellerReportURL(uuid, '1', OTHER_ORIGIN1)]);
}, 'Run auction in cross-origin iframe and open winning ad in a fenced frame child of another cross-origin iframe.');

////////////////////////////////////////////////////////////////////////////////
// Other tests.
////////////////////////////////////////////////////////////////////////////////

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  let iframe = await createIframe(test, OTHER_ORIGIN1, "run-ad-auction");

  // Do everything in a cross-origin iframe, and make sure correct top-frame origin is used.
  await runInFrame(
      test, iframe,
      `const uuid = "${uuid}";
       const renderURL = createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'hostname');

       await joinInterestGroup(
          test_instance, uuid,
          { trustedBiddingSignalsKeys: ['hostname'],
            trustedBiddingSignalsURL: TRUSTED_BIDDING_SIGNALS_URL,
            ads: [{ renderURL: renderURL }],
            biddingLogicURL: createBiddingScriptURL({
              generateBid:
                  \`if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                      throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                    if (trustedBiddingSignals.hostname !== '${window.location.hostname}')
                      throw 'Wrong hostname: ' + trustedBiddingSignals.hostname;\`})});

       await runBasicFledgeTestExpectingWinner(
           test_instance, uuid,
           { trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
            decisionLogicURL:
            createDecisionScriptURL(
              uuid,
              { scoreAd:
                    \`if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                        throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                      if (trustedScoringSignals.renderURL["\${renderURL}"] !== '${window.location.hostname}')
                        throw 'Wrong hostname: ' + trustedScoringSignals.renderURL["\${renderURL}"];\` })});`);
}, 'Different top-frame origin.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  let bidderOrigin = OTHER_ORIGIN1;
  let sellerOrigin = OTHER_ORIGIN2;
  let bidderSendReportToURL = createBidderReportURL(uuid, '1', OTHER_ORIGIN3);
  let sellerSendReportToURL = createSellerReportURL(uuid, '2', OTHER_ORIGIN4);
  let bidderBeaconURL = createBidderBeaconURL(uuid, '3', OTHER_ORIGIN5);
  let sellerBeaconURL = createSellerBeaconURL(uuid, '4', OTHER_ORIGIN6);
  let renderURL = createRenderURL(
      uuid,
      `window.fence.reportEvent({
         eventType: "beacon",
         eventData: window.location.href,
         destination: ["buyer", "seller"]
       })`,
       /*signalsParams=*/null, OTHER_ORIGIN7);

  let iframe = await createIframe(test, bidderOrigin, "join-ad-interest-group");
  let interestGroup = createInterestGroupForOrigin(
      uuid, bidderOrigin,
      {biddingLogicURL: createBiddingScriptURL(
        { origin: bidderOrigin,
          generateBid: `if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                          throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                        if (interestGroup.owner !== "${bidderOrigin}")
                          throw "Wrong origin: " + interestGroup.owner;
                        if (!interestGroup.biddingLogicURL.startsWith("${bidderOrigin}"))
                          throw "Wrong origin: " + interestGroup.biddingLogicURL;
                        if (interestGroup.ads[0].renderURL !== "${renderURL}")
                          throw "Wrong renderURL: " + interestGroup.ads[0].renderURL;
                        if (browserSignals.seller !== "${sellerOrigin}")
                          throw "Wrong origin: " + browserSignals.seller;`,
          reportWin: `if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                        throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                      if (browserSignals.seller !== "${sellerOrigin}")
                        throw "Wrong seller: " + browserSignals.seller;
                      if (browserSignals.interestGroupOwner !== "${bidderOrigin}")
                        throw "Wrong interestGroupOwner: " + browserSignals.interestGroupOwner;
                      if (browserSignals.renderURL !== "${renderURL}")
                        throw "Wrong renderURL: " + browserSignals.renderURL;
                      if (browserSignals.seller !== "${sellerOrigin}")
                        throw "Wrong seller: " + browserSignals.seller;
                      sendReportTo("${bidderSendReportToURL}");
                      registerAdBeacon({beacon: "${bidderBeaconURL}"});` }),
       ads: [{ renderURL: renderURL }]});
  await runInFrame(
      test, iframe,
      `await joinInterestGroup(test_instance, "${uuid}", ${JSON.stringify(interestGroup)});`);

  await runBasicFledgeAuctionAndNavigate(test, uuid,
    { seller: sellerOrigin,
      interestGroupBuyers: [bidderOrigin],
      decisionLogicURL: createDecisionScriptURL(
        uuid,
        { origin: sellerOrigin,
          scoreAd: `if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                      throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                    if (auctionConfig.seller !== "${sellerOrigin}")
                      throw "Wrong seller: " + auctionConfig.seller;
                    if (auctionConfig.interestGroupBuyers[0] !== "${bidderOrigin}")
                      throw "Wrong interestGroupBuyers: " + auctionConfig.interestGroupBuyers;
                    if (browserSignals.interestGroupOwner !== "${bidderOrigin}")
                      throw "Wrong interestGroupOwner: " + browserSignals.interestGroupOwner;
                    if (browserSignals.renderURL !== "${renderURL}")
                      throw "Wrong renderURL: " + browserSignals.renderURL;`,
          reportResult: `if (browserSignals.topWindowHostname !== "${document.location.hostname}")
                           throw "Wrong topWindowHostname: " + browserSignals.topWindowHostname;
                         if (browserSignals.interestGroupOwner !== "${bidderOrigin}")
                           throw "Wrong interestGroupOwner: " + browserSignals.interestGroupOwner;
                         if (browserSignals.renderURL !== "${renderURL}")
                           throw "Wrong renderURL: " + browserSignals.renderURL;
                         sendReportTo("${sellerSendReportToURL}");
                         registerAdBeacon({beacon: "${sellerBeaconURL}"});`})
     });

  await waitForObservedRequests(
      uuid,
      [ bidderSendReportToURL,
        sellerSendReportToURL,
        `${bidderBeaconURL}, body: ${renderURL}`,
        `${sellerBeaconURL}, body: ${renderURL}`
      ]);
}, 'Single seller auction with as many distinct origins as possible (except no component ads).');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);

  // Join an interest group and run an auction with a winner. Use a tracking
  // URL for the ad, so that if it's incorrectly loaded in this test, the
  // waitForObservedRequests() at the end of the test will see it, and the
  // test will fail.
  await joinInterestGroup(
      test, uuid,
      {ads: [{renderURL: createTrackerURL(window.location.origin, uuid, 'track_get', 'renderURL')}]});
  let config = await runBasicFledgeTestExpectingWinner(test, uuid);

  // Try to navigate a fenced frame to the winning ad in a new same-origin
  // window. This should fail. Unfortunately, there's no assertion that
  // can be checked for, and can't communicate with the contents of the
  // fenced frame to make sure the load fails.
  //
  // So instead, join an interest group with a different sendReportTo-url,
  // overwriting the previously joined one, and run another auction, loading
  // the winner in another fenced frame.
  //
  // Then wait to see that only the reporting URLs from that second auction
  // are requested. They should almost always be requested after the URLs
  // from the first auction.
  let child_window =
      await createFrame(test, document.location.origin, /*is_iframe=*/false);
  await runInFrame(
      test, child_window,
      `await createAndNavigateFencedFrame(test_instance, param);
       await joinInterestGroup(
          test_instance, "${uuid}",
          {biddingLogicURL: createBiddingScriptURL(
              {reportWin: "sendReportTo('${createBidderReportURL(uuid, "2")}');" })});
       await runBasicFledgeAuctionAndNavigate(test_instance, "${uuid}");`,
      /*param=*/config);
  await waitForObservedRequests(
      uuid, [createBidderReportURL(uuid, "2"), createSellerReportURL(uuid)]);
}, 'Run auction in main frame, try to open winning ad in different same-origin main frame.');
