chromium/content/test/data/interest_group/bidding_argument_validator.js

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function generateBid(
    interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals,
    browserSignals, directFromSellerSignals) {
  validateInterestGroup(interestGroup);
  validateAuctionSignals(auctionSignals);
  validatePerBuyerSignals(perBuyerSignals);
  validateTrustedBiddingSignals(trustedBiddingSignals);
  validateBrowserSignals(browserSignals, /*isGenerateBid=*/true);
  validateDirectFromSellerSignals(directFromSellerSignals);

  // Bid 2 to outbid other parties bidding 1 at auction.
  const ad = interestGroup.ads[0];
  return {
      'ad': ad,
      'bid': 2,
      'adCost': 3,
      'bidCurrency':
          interestGroup.owner.startsWith("https://a.test") ? 'USD' : 'CAD',
      'render': ad.renderURL,
      'adComponents': [interestGroup.adComponents[0].renderURL]
  };
}

function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
                   browserSignals, directFromSellerSignals) {
  validateAuctionSignals(auctionSignals);
  validatePerBuyerSignals(perBuyerSignals);
  validateSellerSignals(sellerSignals);
  validateBrowserSignals(browserSignals, /*isGenerateBid=*/false);
  validateDirectFromSellerSignals(directFromSellerSignals);

  sendReportTo(browserSignals.interestGroupOwner + '/echo?report_bidder');
}

function validateInterestGroup(interestGroup) {
  if (!interestGroup)
    throw 'No interest group';

  if (Object.keys(interestGroup).length !== 18) {
    throw 'Wrong number of interestGroupFields ' +
        JSON.stringify(interestGroup);
  }

  if (interestGroup.name !== 'cars')
    throw 'Wrong interestGroup.name ' + interestGroup.name;
  if (!interestGroup.owner.startsWith('https://a.test'))
    throw 'Missing a.test in owner ' + interestGroup.owner;

  // Note that this field is deprecated.
  if (interestGroup.useBiddingSignalsPrioritization !== true) {
    throw 'Incorrect useBiddingSignalsPrioritization ' +
        interestGroup.useBiddingSignalsPrioritization;
  }

  if (interestGroup.enableBiddingSignalsPrioritization !== true) {
    throw 'Incorrect enableBiddingSignalsPrioritization ' +
        interestGroup.enableBiddingSignalsPrioritization;
  }

  if (Object.keys(interestGroup.priorityVector).length !== 2 ||
      interestGroup.priorityVector['foo'] !== 2 ||
      interestGroup.priorityVector['bar'] !== -11) {
    throw 'Incorrect priorityVector ' +
        JSON.stringify(interestGroup.priorityVector);
  }

  if (!interestGroup.biddingLogicURL.startsWith('https://a.test') ||
      !interestGroup.biddingLogicURL.endsWith(
          '/bidding_argument_validator.js')) {
    throw 'Incorrect biddingLogicURL ' + interestGroup.biddingLogicURL;
  }

  if (!interestGroup.biddingLogicUrl.startsWith('https://a.test') ||
      !interestGroup.biddingLogicUrl.endsWith(
          '/bidding_argument_validator.js')) {
    throw 'Incorrect biddingLogicUrl ' + interestGroup.biddingLogicUrl;
  }

  if (!interestGroup.updateURL.startsWith('https://a.test') ||
      !interestGroup.updateURL.endsWith('/not_found_update_url.json')) {
    throw 'Incorrect updateURL ' + interestGroup.updateURL;
  }

  if (interestGroup.updateUrl !== interestGroup.updateURL) {
    throw 'Incorrect updateUrl ' + interestGroup.updateUrl;
  }

  // TODO(crbug.com/40258629): Remove this block and decrease number of
  // expected keys above when removing support for dailyUpdateUrl.
  if (!interestGroup.dailyUpdateUrl.startsWith('https://a.test') ||
      !interestGroup.dailyUpdateUrl.endsWith('/not_found_update_url.json')) {
    throw 'Incorrect dailyUpdateUrl ' + interestGroup.dailyUpdateUrl;
  }

  if (interestGroup.executionMode !== 'compatibility')
    throw 'Incorrect executionMode ' + interestGroup.executionMode;

  if (!interestGroup.trustedBiddingSignalsURL.startsWith('https://a.test') ||
      (!interestGroup.trustedBiddingSignalsURL.includes(
          'trusted_bidding_signals.json') &&
       // TODO(mmenke): Remove this once v1 format is no longer supported.
       !interestGroup.trustedBiddingSignalsURL.includes(
          'trusted_bidding_signals_v1.json'))) {
    throw 'Incorrect trustedBiddingSignalsURL ' +
        interestGroup.trustedBiddingSignalsURL;
  }

  if (!interestGroup.trustedBiddingSignalsUrl.startsWith('https://a.test') ||
      (!interestGroup.trustedBiddingSignalsUrl.includes(
          'trusted_bidding_signals.json') &&
       // TODO(mmenke): Remove this once v1 format is no longer supported.
       !interestGroup.trustedBiddingSignalsUrl.includes(
          'trusted_bidding_signals_v1.json'))) {
    throw 'Incorrect trustedBiddingSignalsUrl ' +
        interestGroup.trustedBiddingSignalsUrl;
  }

  trustedBiddingSignalsKeysJson =
      JSON.stringify(interestGroup.trustedBiddingSignalsKeys);
  if (trustedBiddingSignalsKeysJson !== "[\"key1\"]") {
    throw 'Incorrect trustedBiddingSignalsKeys ' +
        trustedBiddingSignalsKeysJson;
  }

  if (interestGroup.trustedBiddingSignalsSlotSizeMode != 'none') {
    throw 'Incorrect trustedBiddingSignalsSlotSizeMode ' +
        interestGroup.trustedBiddingSignalsSlotSizeMode;
  }

  // If userBiddingSignals is passed as a JSON string instead of an object,
  // stringify() will wrap it in another layer of quotes, causing the test to
  // fail. The order of properties produced by stringify() isn't guaranteed by
  // the ECMAScript standard, but some sites depend on the V8 behavior of
  // serializing in declaration order.
  const userBiddingSignalsJSON =
      JSON.stringify(interestGroup.userBiddingSignals);
  if (userBiddingSignalsJSON !== '{"some":"json","stuff":{"here":[1,2]}}')
    throw 'Wrong userBiddingSignals ' + userBiddingSignalsJSON;

  if (interestGroup.ads.length !== 1)
    throw 'Wrong ads.length ' + interestGroup.ads.length;
  if (interestGroup.ads[0].renderURL !== 'https://example.com/render')
    throw 'Wrong ads[0].renderURL ' + interestGroup.ads[0].renderURL;
  if (interestGroup.ads[0].renderUrl !== 'https://example.com/render')
    throw 'Wrong ads[0].renderUrl ' + interestGroup.ads[0].renderUrl;
  const adMetadataJSON = JSON.stringify(interestGroup.ads[0].metadata);
  if (adMetadataJSON !== '{"ad":"metadata","here":[1,2,3]}')
    throw 'Wrong ad[0].metadata ' + adMetadataJSON;

  if (interestGroup.adComponents.length !== 1)
    throw 'Wrong adComponents.length ' + interestGroup.adComponents.length;
  if (interestGroup.adComponents[0].renderURL !==
        'https://example.com/render-component') {
    throw 'Wrong adComponents[0].renderURL ' +
        interestGroup.adComponents[0].renderURL;
  }
  if (interestGroup.adComponents[0].renderUrl !==
        'https://example.com/render-component') {
    throw 'Wrong adComponents[0].renderUrl ' +
        interestGroup.adComponents[0].renderUrl;
  }
  if (interestGroup.adComponents[0].metadata !== undefined) {
    throw 'interestGroup.adComponents[0].metadata ' +
        adMetadataJinterestGroup.adComponents[0].metadataSON;
  }
}

function validateAuctionSignals(auctionSignals) {
  const auctionSignalsJSON = JSON.stringify(auctionSignals);
  if (auctionSignalsJSON !== '{"so":"I","hear":["you","like","json"]}')
    throw 'Wrong auctionSignals ' + auctionSignalsJSON;
}

function validatePerBuyerSignals(perBuyerSignals) {
  const perBuyerSignalsJson = JSON.stringify(perBuyerSignals);
  if (perBuyerSignalsJson !== '{"signalsForBuyer":1}')
    throw 'Wrong perBuyerSignals ' + perBuyerSignalsJson;
}

function validateDirectFromSellerSignals(directFromSellerSignals) {
  const perBuyerSignalsJSON =
      JSON.stringify(directFromSellerSignals.perBuyerSignals);
  if (perBuyerSignalsJSON !== '{"json":"for","buyer":[1]}' &&
      perBuyerSignalsJSON !== '{"buyer":[1],"json":"for"}') {
    throw 'Wrong directFromSellerSignals.perBuyerSignals ' +
        perBuyerSignalsJSON;
  }
  const auctionSignalsJSON =
      JSON.stringify(directFromSellerSignals.auctionSignals);
  if (auctionSignalsJSON !== '{"json":"for","all":["parties"]}' &&
      auctionSignalsJSON !== '{"all":["parties"],"json":"for"}') {
    throw 'Wrong directFromSellerSignals.auctionSignals ' +
        auctionSignalsJSON;
  }
}

function validateTrustedBiddingSignals(trustedBiddingSignals) {
  const trustedBiddingSignalsJSON = JSON.stringify(trustedBiddingSignals);
  if (trustedBiddingSignalsJSON !== '{"key1":"1"}')
    throw 'Wrong trustedBiddingSignals ' + trustedBiddingSignalsJSON;
}

function validateBrowserSignals(browserSignals, isGenerateBid) {
  // Common fields for generateBid() and reportWin().
  if (browserSignals.topWindowHostname !== 'c.test')
    throw 'Wrong topWindowHostname ' + browserSignals.topWindowHostname;
  if (!browserSignals.seller.startsWith('https://b.test'))
    throw 'Wrong seller ' + browserSignals.seller;
  if ('topLevelSeller' in browserSignals)
    throw 'Wrong topLevelSeller ' + browserSignals.topLevelSeller;

  if (isGenerateBid) {
    if (Object.keys(browserSignals).length !== 10) {
      throw 'Wrong number of browser signals fields ' +
          JSON.stringify(browserSignals);
    }
    if (browserSignals.joinCount !== 1)
      throw 'Wrong joinCount ' + browserSignals.joinCount;
    if (browserSignals.bidCount !== 0)
      throw 'Wrong bidCount ' + bidCount;
    if (browserSignals.prevWins.length !== 0)
      throw 'Wrong prevWins ' + JSON.stringify(browserSignals.prevWins);
    if (browserSignals.prevWinsMs.length !== 0)
      throw 'Wrong prevWinsMs ' + JSON.stringify(browserSignals.prevWinsMs);
    if (browserSignals.adComponentsLimit !== 40)
      throw 'Wrong adComponentsLimit ' + browserSignals.adComponentsLimit;
    if (browserSignals.forDebuggingOnlyInCooldownOrLockout)
      throw 'Wrong forDebuggingOnlyInCooldownOrLockout ' +
          browserSignals.forDebuggingOnlyInCooldownOrLockout;
    if (browserSignals.multiBidLimit !== 1)
      throw 'Wrong multiBidLimit ' + browserSignals.multiBidLimit;
  } else {
    // FledgePassKAnonStatusToReportWin feature adds a new parameter
    // KAnonStatus to reportWin(), which is under a Finch trial for some enabled
    // tests.
    // TODO(xtlsheep): Check length only equals to 16 after
    // FledgePassKAnonStatusToReportWin is completely turned on.
    if (Object.keys(browserSignals).length !== 15 &&
        Object.keys(browserSignals).length !== 16) {
      throw 'Wrong number of browser signals fields ' +
          JSON.stringify(browserSignals);
    }
    if (!browserSignals.interestGroupOwner.startsWith('https://a.test'))
      throw 'Wrong interestGroupOwner ' + browserSignals.interestGroupOwner;
    if (browserSignals.interestGroupName !== 'cars')
      throw 'Wrong interestGroupName ' + browserSignals.interestGroupName;
    if (browserSignals.renderURL !== "https://example.com/render")
      throw 'Wrong renderURL ' + browserSignals.renderURL;
    if (browserSignals.renderUrl !== "https://example.com/render")
      throw 'Wrong renderUrl ' + browserSignals.renderUrl;
    if (browserSignals.bid !== 2)
      throw 'Wrong bid ' + browserSignals.bid;
    if (browserSignals.bidCurrency !== 'USD')
      throw 'Wrong bidCurrency ' + browserSignals.bidCurrency;
    if (browserSignals.highestScoringOtherBid !== 0) {
      throw 'Wrong highestScoringOtherBid ' +
          browserSignals.highestScoringOtherBid;
    }
    if (browserSignals.highestScoringOtherBidCurrency !== 'EUR') {
      throw 'Wrong highestScoringOtherBidCurrency ' +
          browserSignals.highestScoringOtherBidCurrency;
    }
    if (browserSignals.adCost !== 3)
      throw 'Wrong adCost ' + browserSignals.adCost;
    if (browserSignals.reportingTimeout !== 2000)
      throw 'Wrong reportingTimeout ' + browserSignals.reportingTimeout;
  }
}

function validateSellerSignals(sellerSignals) {
  const sellerSignalsJson = JSON.stringify(sellerSignals);
  if (sellerSignalsJson !== '["seller signals for winner"]')
    throw 'Wrong sellerSignals ' + sellerSignals;
}