// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
// META: script=/common/utils.js
// META: script=resources/ba-fledge-util.sub.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-4
// META: variant=?5-8
// META: variant=?9-12
// META: variant=?13-16
// META: variant=?17-last

"use strict";

// These tests focus on the navigator.getInterestGroupAdAuctionData() method.

// Checks various fields for decoded InterestGroupAdAuctionData that's
// one IG owned by this origin, and returns that IG's info.
function validateWithOneIg(decoded) {
  assert_equals(decoded.message.version, 0);
  assert_equals(decoded.message.publisher, window.location.hostname);
  assert_equals(typeof decoded.message.generationId, 'string');
  let igMapKeys = Object.getOwnPropertyNames(decoded.message.interestGroups);
  assert_array_equals(igMapKeys, [window.location.origin]);
  let igInfo = decoded.message.interestGroups[window.location.origin];
  assert_true(igInfo instanceof Array);
  assert_equals(igInfo.length, 1, 'number of IGs');
  return igInfo[0];
}

subsetTest(promise_test, async test => {
  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length === 0);
}, 'getInterestGroupAdAuctionData() with no interest groups returns a zero length result.');

async function testInvalidConfig(test, configObj, desc) {
  if (!configObj.coordinatorOrigin) {
    configObj.coordinatorOrigin = await BA.configureCoordinator();
  }
  await promise_rejects_js(
      test, TypeError, navigator.getInterestGroupAdAuctionData(configObj),
      desc);
}

subsetTest(promise_test, async test => {
  await testInvalidConfig(test, {}, 'no seller');
  await testInvalidConfig(test, {seller: 'example'}, 'invalid seller 1');
  await testInvalidConfig(
      test, {seller: 'http://example.org'}, 'invalid seller 2');
  await testInvalidConfig(
      test, {seller: 'https://example.org', coordinatorOrigin: 'example'},
      'invalid coordinator 1');
  await testInvalidConfig(
      test, {seller: 'https://example.org', coordinatorOrigin: 'example.org'},
      'invalid coordinator 2');

  await testInvalidConfig(
      test, {seller: 'https://example.org', perBuyerConfig: {'a': {}}},
      'invalid buyer 1');

  await testInvalidConfig(
      test,
      {seller: 'https://example.org', perBuyerConfig: {'http://a.com': {}}},
      'invalid buyer 2');

  await testInvalidConfig(
      test, {
        seller: 'https://example.org',
        perBuyerConfig: {'https://a.com': {}, 'http://b.com': {}}
      },
      'invalid buyer 3');

  await testInvalidConfig(
      test, {
        seller: 'https://example.org',
        perBuyerConfig: {'https://a.com': {}, 'https://b.com': {}}
      },
      'missing size info w/per-buyer config 1');

  await testInvalidConfig(
      test, {
        seller: 'https://example.org',
        perBuyerConfig:
            {'https://a.com': {targetSize: 400}, 'https://b.com': {}}
      },
      'missing size info w/per-buyer config 2');

  // These two actually succeed.
  let result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: 'https://example.org',
    perBuyerConfig:
        {'https://a.com': {targetSize: 400}, 'https://b.com': {targetSize: 500}}
  });
  assert_true(result.requestId !== null);

  result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: 'https://example.org',
    perBuyerConfig: {'https://a.com': {targetSize: 400}, 'https://b.com': {}},
    requestSize: 5000
  });
  assert_true(result.requestId !== null);
}, 'getInterestGroupAdAuctionData() config checks');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);

  // Check that the required field and one IG (but no ad data) are here,
  // since we neither provided adRenderIds or asked for full data to be
  // included.
  let ig = validateWithOneIg(decoded);
  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_array_equals(ig.ads, []);
  assert_equals(ig.browserSignals.joinCount, 1, 'joinCount');
  assert_equals(ig.browserSignals.bidCount, 0, 'bidCount');
  assert_array_equals(ig.browserSignals.prevWins, []);
}, 'getInterestGroupAdAuctionData() with one interest group returns a valid result.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  await joinInterestGroup(test, uuid, {
    ads: [
      {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'a'},
      {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'b'}
    ]
  });

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  // This ig should have two ads with adRenderIds, but not URLs.
  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_array_equals(ig.ads, ['a', 'b']);
  assert_equals(ig.browserSignals.joinCount, 1, 'joinCount');
  assert_equals(ig.browserSignals.bidCount, 0, 'bidCount');
  assert_array_equals(ig.browserSignals.prevWins, []);
}, 'getInterestGroupAdAuctionData() with one interest group with two ads w/renderIds.');

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  await joinInterestGroup(test, uuid, {
    auctionServerRequestFlags: ['omit-ads'],
    ads: [
      {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'a'},
      {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'b'}
    ]
  });

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_not_own_property(ig, 'ads', 'no ads expected');
  assert_equals(ig.browserSignals.joinCount, 1, 'joinCount');
}, 'getInterestGroupAdAuctionData() with one interest group with two ads w/renderIds and omit-ads.');

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

  const adsArray = [
    {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'a'},
    {renderURL: createRenderURL(uuid) + '&a', adRenderId: 'b'}
  ];
  await joinInterestGroup(
      test, uuid,
      {auctionServerRequestFlags: ['include-full-ads'], ads: adsArray});

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  // Since include-full-ads is on, this gets entire objects, not just
  // adRenderId.
  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_equals(ig.ads.length, 2, '# of ads');
  assert_equals(ig.ads[0].renderURL, adsArray[0].renderURL, 'renderURL 0');
  assert_equals(ig.ads[1].renderURL, adsArray[1].renderURL, 'renderURL 1');
  assert_equals(ig.ads[0].adRenderId, adsArray[0].adRenderId, 'adRenderId 0');
  assert_equals(ig.ads[1].adRenderId, adsArray[1].adRenderId, 'adRenderId 1');
  assert_equals(ig.browserSignals.joinCount, 1, 'joinCount');
}, 'getInterestGroupAdAuctionData() with one interest group with two ads w/renderIds and include-full-ads.');

// Returns an AuctionAdInterestGroup that sets all fields that can be exported
// via getInterestGroupAdAuctionData().
function makeTemplateIgConfig(uuid) {
  const adsArray = [
    {
      renderURL: createRenderURL(uuid) + '&a',
      adRenderId: 'a',
      metadata: 'ada',
      sizeGroup: 'small'
    },
    {
      renderURL: createRenderURL(uuid) + '&b',
      adRenderId: 'b',
      metadata: 'adb',
      sizeGroup: 'big'
    }
  ];
  const adComponentsArray = [
    {
      renderURL: 'https://example.org/ca',
      adRenderId: 'ca',
      metadata: 'compa',
      sizeGroup: 'big'
    },
    {
      renderURL: 'https://example.org/cb',
      adRenderId: 'cb',
      metadata: 'compb',
      sizeGroup: 'small'
    },
    {
      renderURL: 'https://example.org/cc',
      adRenderId: 'cc',
      metadata: 'compc',
      sizeGroup: 'big'
    },
  ];
  return {
    ads: adsArray,
    adComponents: adComponentsArray,
    adSizes: {
      's': {width: '100px', height: '30px'},
      'xl': {width: '1000px', height: '300px'}
    },
    sizeGroups: {'small': ['s'], 'big': ['xl']},
    trustedBiddingSignalsKeys: ['alpha', 'beta'],
    userBiddingSignals: 14
  };
}

subsetTest(promise_test, async test => {
  const uuid = generateUuid(test);
  const igConfig = makeTemplateIgConfig(uuid);
  igConfig.auctionServerRequestFlags = ['include-full-ads'];
  await joinInterestGroup(test, uuid, igConfig);

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_true(ig.ads instanceof Array);
  assert_equals(ig.ads.length, 2, '# of ads');
  assert_equals(ig.ads[0].renderURL, igConfig.ads[0].renderURL, 'renderURL 0');
  assert_equals(ig.ads[1].renderURL, igConfig.ads[1].renderURL, 'renderURL 1');
  assert_equals(ig.ads[0].adRenderId, 'a', 'adRenderId 0');
  assert_equals(ig.ads[1].adRenderId, 'b', 'adRenderId 1');
  assert_equals(ig.ads[0].metadata, '"ada"', 'metadata 0');
  assert_equals(ig.ads[1].metadata, '"adb"', 'metadata 1');
  assert_equals(ig.ads[0].sizeGroup, 'small', 'sizegroup 0');
  assert_equals(ig.ads[1].sizeGroup, 'big', 'sizegroup 1');

  assert_true(ig.components instanceof Array);
  assert_equals(ig.components.length, 3, '# of component ads');
  assert_equals(
      ig.components[0].renderURL, igConfig.adComponents[0].renderURL,
      'component renderURL 0');
  assert_equals(
      ig.components[1].renderURL, igConfig.adComponents[1].renderURL,
      'component renderURL 1');
  assert_equals(
      ig.components[2].renderURL, igConfig.adComponents[2].renderURL,
      'component renderURL 2');
  assert_equals(ig.components[0].adRenderId, 'ca', 'component adRenderId 0');
  assert_equals(ig.components[1].adRenderId, 'cb', 'component adRenderId 1');
  assert_equals(ig.components[2].adRenderId, 'cc', 'component adRenderId 2');
  assert_equals(ig.components[0].metadata, '"compa"', 'component metadata 0');
  assert_equals(ig.components[1].metadata, '"compb"', 'component metadata 1');
  assert_equals(ig.components[2].metadata, '"compc"', 'component metadata 2');
  assert_equals(ig.components[0].sizeGroup, 'big', 'component sizegroup 0');
  assert_equals(ig.components[1].sizeGroup, 'small', 'component sizegroup 1');
  assert_equals(ig.components[2].sizeGroup, 'big', 'component sizegroup 2');

  assert_true(ig.biddingSignalsKeys instanceof Array);
  assert_array_equals(ig.biddingSignalsKeys, ['alpha', 'beta']);
  assert_equals(ig.userBiddingSignals, '14');
}, 'getInterestGroupAdAuctionData() all IG data fields, with include-full-ads');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_array_equals(ig.ads, ['a', 'b']);

  assert_true(ig.components instanceof Array);
  assert_array_equals(ig.ads, ['a', 'b']);
  assert_array_equals(ig.components, ['ca', 'cb', 'cc']);

  assert_array_equals(ig.biddingSignalsKeys, ['alpha', 'beta']);
  assert_equals(ig.userBiddingSignals, '14');
}, 'getInterestGroupAdAuctionData() all IG data fields, w/o include-full-ads');

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

  const igConfig = makeTemplateIgConfig(uuid);
  igConfig.auctionServerRequestFlags = ['omit-user-bidding-signals'];
  await joinInterestGroup(test, uuid, igConfig);

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);

  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_array_equals(ig.ads, ['a', 'b']);

  assert_true(ig.components instanceof Array);
  assert_array_equals(ig.ads, ['a', 'b']);
  assert_array_equals(ig.components, ['ca', 'cb', 'cc']);

  assert_array_equals(ig.biddingSignalsKeys, ['alpha', 'beta']);
  assert_false('userBiddingSignals' in ig, 'userBiddingSignals');
}, 'getInterestGroupAdAuctionData() all IG data fields, with omit-user-bidding-signals');

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

  const igConfig = makeTemplateIgConfig(uuid);

  // Join twice.
  await joinInterestGroup(test, uuid, igConfig);
  await joinInterestGroup(test, uuid, igConfig);

  // And run an auction. This is a local auction, not a B&A one, run to update
  // bid/win stats.
  await runBasicFledgeAuctionAndNavigate(test, uuid);
  await waitForObservedRequests(
      uuid, [createBidderReportURL(uuid), createSellerReportURL(uuid)]);

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);
  assert_equals(ig.browserSignals.joinCount, 2, 'joinCount');
  assert_equals(ig.browserSignals.bidCount, 1, 'bidCount');

  // RecencyMs is the # of milliseconds since the join. We can't exactly say
  // what it is, but it shouldn't be too huge.
  assert_true(typeof ig.browserSignals.recencyMs === 'number');
  assert_between_inclusive(
      ig.browserSignals.recencyMs, 0, 60000,
      'RecencyMs is between 0 and 60 seconds');
  // It's also supposed to be an integer.
  assert_equals(
      ig.browserSignals.recencyMs, Math.round(ig.browserSignals.recencyMs),
      'RecencyMs is an integer');

  // One win. The format here depends highly on whether full ads are used or
  // not.
  assert_true(
      ig.browserSignals.prevWins instanceof Array, 'prevWins is an array');
  assert_equals(ig.browserSignals.prevWins.length, 1, 'prevWins length');
  assert_true(
      ig.browserSignals.prevWins[0] instanceof Array,
      'prevWins[0] is an array');
  assert_equals(ig.browserSignals.prevWins[0].length, 2, 'prevWins[0] length');

  // prevWins[0][0] is the time delta in second again.
  let prevWinTime = ig.browserSignals.prevWins[0][0];
  assert_true(typeof prevWinTime === 'number');
  assert_between_inclusive(
      prevWinTime, 0, 60, 'prevWinTime is between 0 and 60 seconds');
  // It's also supposed to be an integer.
  assert_equals(
      prevWinTime, Math.round(prevWinTime), 'prevWinTime is an integer');

  // prevWins[0][1] is the adRenderId of the winner.
  assert_equals(ig.browserSignals.prevWins[0][1], 'a');
}, 'getInterestGroupAdAuctionData() browserSignals');

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

  const igConfig = makeTemplateIgConfig(uuid);
  igConfig.auctionServerRequestFlags = ['include-full-ads'];

  // Join twice.
  await joinInterestGroup(test, uuid, igConfig);
  await joinInterestGroup(test, uuid, igConfig);

  // And run an auction. This is a local auction, not a B&A one, run to update
  // bid/win stats.
  await runBasicFledgeAuctionAndNavigate(test, uuid);
  await waitForObservedRequests(
      uuid, [createBidderReportURL(uuid), createSellerReportURL(uuid)]);

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  let ig = validateWithOneIg(decoded);
  assert_equals(ig.browserSignals.joinCount, 2, 'joinCount');
  assert_equals(ig.browserSignals.bidCount, 1, 'bidCount');

  // RecencyMs is the # of milliseconds since the join. We can't exactly say
  // what it is, but it shouldn't be too huge.
  assert_true(typeof ig.browserSignals.recencyMs === 'number');
  assert_between_inclusive(
      ig.browserSignals.recencyMs, 0, 60000,
      'RecencyMs is between 0 and 60 seconds');
  // It's also supposed to be an integer.
  assert_equals(
      ig.browserSignals.recencyMs, Math.round(ig.browserSignals.recencyMs),
      'RecencyMs is an integer');

  // One win. The format here depends highly on whether full ads are used or
  // not.
  assert_true(
      ig.browserSignals.prevWins instanceof Array, 'prevWins is an array');
  assert_equals(ig.browserSignals.prevWins.length, 1, 'prevWins length');
  assert_true(
      ig.browserSignals.prevWins[0] instanceof Array,
      'prevWins[0] is an array');
  assert_equals(ig.browserSignals.prevWins[0].length, 2, 'prevWins[0] length');

  // prevWins[0][0] is the time delta in second again.
  let prevWinTime = ig.browserSignals.prevWins[0][0];
  assert_true(typeof prevWinTime === 'number');
  assert_between_inclusive(
      prevWinTime, 0, 60, 'prevWinTime is between 0 and 60 seconds');
  // It's also supposed to be an integer.
  assert_equals(
      prevWinTime, Math.round(prevWinTime), 'prevWinTime is an integer');

  // prevWins[0][1] is an ad object w/include-full-ads on (with renderURL,
  // metadata, and adRenderId).
  let prevWinAd = ig.browserSignals.prevWins[0][1];
  assert_equals(
      prevWinAd.renderURL, igConfig.ads[0].renderURL, 'prevWin ad renderURL');
  assert_equals(prevWinAd.metadata, '"ada"', 'prevWin ad metadata');
  assert_equals(prevWinAd.adRenderId, 'a', 'prevWin ad adRenderId');
}, 'getInterestGroupAdAuctionData() browserSignals with include-full-ads');

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

  const igTemplate = makeTemplateIgConfig(uuid);
  await joinInterestGroup(test, uuid, {...igTemplate, name: 'first'});
  await joinInterestGroup(test, uuid, {...igTemplate, name: 'second'});

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  assert_equals(decoded.message.version, 0);
  assert_equals(decoded.message.publisher, window.location.hostname);
  assert_equals(typeof decoded.message.generationId, 'string');

  let origin = window.location.origin;
  let igMapKeys = Object.getOwnPropertyNames(decoded.message.interestGroups);
  assert_array_equals(igMapKeys, [origin]);
  assert_equals(decoded.message.interestGroups[origin].length, 2);
  let names = [
    decoded.message.interestGroups[origin][0].name,
    decoded.message.interestGroups[origin][1].name
  ];
  assert_array_equals(names.sort(), ['first', 'second']);
}, 'getInterestGroupAdAuctionData() with multiple interest groups');

async function joinCrossOriginIG(test, uuid, origin, name) {
  let iframe = await createIframe(test, origin, 'join-ad-interest-group');
  await runInFrame(
      test, iframe,
      `await joinInterestGroup(test_instance, "${uuid}", {name: "${name}"});`);
}

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

  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN1, 'o1');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN2, 'o2');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN3, 'o3');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN4, 'o4');

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  assert_equals(decoded.message.version, 0);
  assert_equals(decoded.message.publisher, window.location.hostname);
  assert_equals(typeof decoded.message.generationId, 'string');
  let igMapKeys = Object.getOwnPropertyNames(decoded.message.interestGroups);
  assert_array_equals(
      igMapKeys.sort(),
      [OTHER_ORIGIN1, OTHER_ORIGIN2, OTHER_ORIGIN3, OTHER_ORIGIN4].sort());
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN1].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN1][0].name, 'o1');
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN2].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN2][0].name, 'o2');
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3][0].name, 'o3');
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN4].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN4][0].name, 'o4');
}, 'getInterestGroupAdAuctionData() with multiple buyers');

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

  let renderURL = createRenderURL(uuid);
  let bidderDebugReportURL =
      createBidderReportURL(uuid, /*id=*/ 'forDebuggingOnly');

  let biddingLogicURL = createBiddingScriptURL({
    generateBid: `
        forDebuggingOnly.reportAdAuctionWin('${bidderDebugReportURL}');
        return {bid: 1, render: '${renderURL}'};`
  });

  await Promise.all([
    joinInterestGroup(
        test, uuid,
        {name: 'o1', ads: [{renderURL: renderURL}], biddingLogicURL}),
    joinInterestGroup(
        test, uuid,
        {name: 'o2', ads: [{renderURL: renderURL}], biddingLogicURL})
  ]);

  // Run an auction to put the origin to cooldown, or lockout.
  await runBasicFledgeAuctionAndNavigate(test, uuid);

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  assert_equals(decoded.message.version, 0);
  assert_equals(decoded.message.publisher, window.location.hostname);
  assert_equals(typeof decoded.message.generationId, 'string');

  assert_equals(
      decoded.message.interestGroups[window.location.origin].length, 2);
  assert_equals(
      decoded.message.interestGroups[window.location.origin][0]
          .inCooldownOrLockout,
      true);
  assert_equals(
      decoded.message.interestGroups[window.location.origin][1]
          .inCooldownOrLockout,
      true);
}, 'getInterestGroupAdAuctionData() InCooldownOrLockout in each IG');

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

  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN1, 'o1');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN2, 'o2');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN3, 'o3');
  await joinCrossOriginIG(test, uuid, OTHER_ORIGIN4, 'o4');

  let config = {
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin,
    perBuyerConfig: {},
    requestSize: 5000
  };
  config.perBuyerConfig[OTHER_ORIGIN2] = {};
  config.perBuyerConfig[OTHER_ORIGIN3] = {};
  const result = await navigator.getInterestGroupAdAuctionData(config);
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);
  assert_equals(decoded.message.version, 0);
  assert_equals(decoded.message.publisher, window.location.hostname);
  assert_equals(typeof decoded.message.generationId, 'string');
  let igMapKeys = Object.getOwnPropertyNames(decoded.message.interestGroups);
  assert_array_equals(igMapKeys.sort(), [OTHER_ORIGIN2, OTHER_ORIGIN3].sort());
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN2].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN2][0].name, 'o2');
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3].length, 1);
  assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3][0].name, 'o3');
}, 'getInterestGroupAdAuctionData() uses perBuyerConfig to select buyers');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: await BA.configureCoordinator(),
    seller: window.location.origin
  });
  assert_true(result.requestId !== null);
  assert_true(result.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(result.request);

  assert_own_property(decoded.message, 'enforceKAnon');
  assert_equals(decoded.message.enforceKAnon, true);
}, 'getInterestGroupAdAuctionData() requests k-anon.');


/*************************************************************************
 * Tests for the multi-seller variant of the API
 *************************************************************************/

 async function validateForSeller(adAuctionData, seller) {
  assert_equals(adAuctionData.seller, seller);
  assert_not_own_property(adAuctionData, 'error');
  assert_own_property(adAuctionData, 'request');
  assert_true(adAuctionData.request.length > 0);

  let decoded = await BA.decodeInterestGroupData(adAuctionData.request);
  let ig = validateWithOneIg(decoded);

  assert_equals(ig.name, DEFAULT_INTEREST_GROUP_NAME);
  assert_array_equals(ig.ads, ['a', 'b']);

  assert_true(ig.components instanceof Array);
  assert_array_equals(ig.components, ['ca', 'cb', 'cc']);

  assert_array_equals(ig.biddingSignalsKeys, ['alpha', 'beta']);
  assert_equals(ig.userBiddingSignals, '14');
}

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

  const result = await navigator.getInterestGroupAdAuctionData({
    sellers: [{
      coordinatorOrigin: await BA.configureCoordinator(),
      seller: window.location.origin,
    }]
  });

  assert_true(result.requestId !== null);
  assert_own_property(result, 'requests');
  assert_equals(result.requests.length, 1);
  await validateForSeller(result.requests[0], window.location.origin);
}, 'getInterestGroupAdAuctionData() multi-seller with single seller');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    sellers: [{
        coordinatorOrigin: await BA.configureCoordinator(),
        seller: window.location.origin,
      }, {
        coordinatorOrigin: await BA.configureCoordinator(),
        seller: OTHER_ORIGIN1,
    }]
  });

  assert_true(result.requestId !== null);
  assert_own_property(result, 'requests');
  assert_equals(result.requests.length, 2, JSON.stringify(result));
  result.requests.sort((a,b)=> (a.seller < b.seller ? -1 : +(a.seller > b.seller)));

  await validateForSeller(result.requests[0], OTHER_ORIGIN1);
  await validateForSeller(result.requests[1], window.location.origin);

  assert_not_equals(result.requests[0].request.toString(), result.requests[1].request.toString());
}, 'getInterestGroupAdAuctionData() multi-seller with multiple sellers - valid');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    sellers: [{
        coordinatorOrigin: await BA.configureCoordinator(),
        seller: window.location.origin,
      }, {
        coordinatorOrigin: "https://invalid.coordinator.test/",
        seller: OTHER_ORIGIN1,
    }]
  });

  assert_true(result.requestId !== null);
  assert_own_property(result, 'requests');
  assert_equals(result.requests.length, 2, JSON.stringify(result));
  result.requests.sort((a,b)=> (a.seller < b.seller ? -1 : +(a.seller > b.seller)));

  assert_equals(result.requests[0].seller, OTHER_ORIGIN1);
  assert_own_property(result.requests[0], 'error');
  assert_not_own_property(result.requests[0], 'request');

  await validateForSeller(result.requests[1], window.location.origin);
}, 'getInterestGroupAdAuctionData() multi-seller with multiple sellers - one invalid coordinator');

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

  await promise_rejects_js(test, TypeError, navigator.getInterestGroupAdAuctionData({
    sellers: [{
        coordinatorOrigin: await BA.configureCoordinator(),
        seller: window.location.origin,
      }, {
        coordinatorOrigin: await BA.configureCoordinator(),
        seller: "http://not.secure.test/",
    }]
  }));
}, 'getInterestGroupAdAuctionData() multi-seller with multiple sellers - one invalid seller');

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

  await promise_rejects_js(test, TypeError, navigator.getInterestGroupAdAuctionData({
  }));
}, 'getInterestGroupAdAuctionData() one of "seller" and "sellers" is required');

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

  await promise_rejects_js(test, TypeError, navigator.getInterestGroupAdAuctionData({
    seller: window.location.origin,
    sellers: [{
      coordinatorOrigin: await BA.configureCoordinator(),
      seller: window.location.origin,
    }]
  }));
}, 'getInterestGroupAdAuctionData() doesn\'t allow "seller" and "sellers" fields');

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

  await promise_rejects_js(test, TypeError, navigator.getInterestGroupAdAuctionData({
    coordinatorOrigin: window.location.origin,
    sellers: [{
      coordinatorOrigin: await BA.configureCoordinator(),
      seller: window.location.origin,
    }]
  }));
}, 'getInterestGroupAdAuctionData() doesn\'t allow "coordinatorOrigin" and "sellers" fields');

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

  await promise_rejects_js(test, TypeError, navigator.getInterestGroupAdAuctionData({
    sellers: [{
      coordinatorOrigin: await BA.configureCoordinator(),
      seller: window.location.origin,
    }, {
      coordinatorOrigin: await BA.configureCoordinator(),
      seller: window.location.origin,
    }
  ]
  }));
}, 'getInterestGroupAdAuctionData() doesn\'t allow duplicate sellers in "sellers" field');

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

  const result = await navigator.getInterestGroupAdAuctionData({
    sellers: []
  });
  assert_equals(result.requestId, "");
  assert_array_equals(result.requests, []);
}, 'getInterestGroupAdAuctionData() with no sellers');
