diff --git a/src/prebid.js b/src/prebid.js index 94391343cd3..0fff218a32f 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -622,7 +622,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @alias module:pbjs.requestBids */ $$PREBID_GLOBAL$$.requestBids = (function() { - const delegate = hook('sync', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2, metrics } = {}) { + const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2, metrics } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); @@ -646,7 +646,7 @@ $$PREBID_GLOBAL$$.requestBids = (function() { }); })(); -export const startAuction = hook('sync', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, ttlBuffer, adUnitCodes, labels, auctionId, ortb2Fragments, metrics } = {}) { +export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, ttlBuffer, adUnitCodes, labels, auctionId, ortb2Fragments, metrics } = {}) { const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = useMetrics(metrics).measureTime('requestBids.validate', () => checkAdUnitSetup(adUnits)); @@ -658,85 +658,80 @@ export const startAuction = hook('sync', function ({ bidsBackHandler, timeout: c adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - return new Promise((resolve) => { - function auctionDone(bids, timedOut, auctionId) { - if (typeof bidsBackHandler === 'function') { - try { - bidsBackHandler(bids, timedOut, auctionId); - } catch (e) { - logError('Error executing bidsBackHandler', null, e); - } - } - resolve({bids, timedOut, auctionId}); - } + /* + * for a given adunit which supports a set of mediaTypes + * and a given bidder which supports a set of mediaTypes + * a bidder is eligible to participate on the adunit + * if it supports at least one of the mediaTypes on the adunit + */ + adUnits.forEach(adUnit => { + // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); + + // get the bidder's mediaTypes + const allBidders = adUnit.bids.map(bid => bid.bidder); + const bidderRegistry = adapterManager.bidderRegistry; + + const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); - /* - * for a given adunit which supports a set of mediaTypes - * and a given bidder which supports a set of mediaTypes - * a bidder is eligible to participate on the adunit - * if it supports at least one of the mediaTypes on the adunit - */ - adUnits.forEach(adUnit => { - // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present - const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); - - // get the bidder's mediaTypes - const allBidders = adUnit.bids.map(bid => bid.bidder); - const bidderRegistry = adapterManager.bidderRegistry; - - const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); - - const tid = adUnit.ortb2Imp?.ext?.tid || generateUUID(); - adUnit.transactionId = tid; - if (ttlBuffer != null && !adUnit.hasOwnProperty('ttlBuffer')) { - adUnit.ttlBuffer = ttlBuffer; + const tid = adUnit.ortb2Imp?.ext?.tid || generateUUID(); + adUnit.transactionId = tid; + if (ttlBuffer != null && !adUnit.hasOwnProperty('ttlBuffer')) { + adUnit.ttlBuffer = ttlBuffer; + } + // Populate ortb2Imp.ext.tid with transactionId. Specifying a transaction ID per item in the ortb impression array, lets multiple transaction IDs be transmitted in a single bid request. + deepSetValue(adUnit, 'ortb2Imp.ext.tid', tid); + + bidders.forEach(bidder => { + const adapter = bidderRegistry[bidder]; + const spec = adapter && adapter.getSpec && adapter.getSpec(); + // banner is default if not specified in spec + const bidderMediaTypes = (spec && spec.supportedMediaTypes) || ['banner']; + + // check if the bidder's mediaTypes are not in the adUnit's mediaTypes + const bidderEligible = adUnitMediaTypes.some(type => includes(bidderMediaTypes, type)); + if (!bidderEligible) { + // drop the bidder from the ad unit if it's not compatible + logWarn(unsupportedBidderMessage(adUnit, bidder)); + adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); + } else { + adunitCounter.incrementBidderRequestsCounter(adUnit.code, bidder); } - // Populate ortb2Imp.ext.tid with transactionId. Specifying a transaction ID per item in the ortb impression array, lets multiple transaction IDs be transmitted in a single bid request. - deepSetValue(adUnit, 'ortb2Imp.ext.tid', tid); - - bidders.forEach(bidder => { - const adapter = bidderRegistry[bidder]; - const spec = adapter && adapter.getSpec && adapter.getSpec(); - // banner is default if not specified in spec - const bidderMediaTypes = (spec && spec.supportedMediaTypes) || ['banner']; - - // check if the bidder's mediaTypes are not in the adUnit's mediaTypes - const bidderEligible = adUnitMediaTypes.some(type => includes(bidderMediaTypes, type)); - if (!bidderEligible) { - // drop the bidder from the ad unit if it's not compatible - logWarn(unsupportedBidderMessage(adUnit, bidder)); - adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); - } else { - adunitCounter.incrementBidderRequestsCounter(adUnit.code, bidder); - } - }); - adunitCounter.incrementRequestsCounter(adUnit.code); }); + adunitCounter.incrementRequestsCounter(adUnit.code); + }); - if (!adUnits || adUnits.length === 0) { - logMessage('No adUnits configured. No bids requested.'); - auctionDone(); - } else { - const auction = auctionManager.createAuction({ - adUnits, - adUnitCodes, - callback: auctionDone, - cbTimeout, - labels, - auctionId, - ortb2Fragments, - metrics, - }); - - let adUnitsLen = adUnits.length; - if (adUnitsLen > 15) { - logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); + if (!adUnits || adUnits.length === 0) { + logMessage('No adUnits configured. No bids requested.'); + if (typeof bidsBackHandler === 'function') { + // executeCallback, this will only be called in case of first request + try { + bidsBackHandler(); + } catch (e) { + logError('Error executing bidsBackHandler', null, e); } - - adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); - auction.callBids(); } + return; + } + + const auction = auctionManager.createAuction({ + adUnits, + adUnitCodes, + callback: bidsBackHandler, + cbTimeout, + labels, + auctionId, + ortb2Fragments, + metrics, }); + + let adUnitsLen = adUnits.length; + if (adUnitsLen > 15) { + logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); + } + + adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); + auction.callBids(); }, 'startAuction'); export function executeCallbacks(fn, reqBidsConfigObj) { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index b8b82f7ca96..68fba532252 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -21,8 +21,7 @@ import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; import $$PREBID_GLOBAL$$ from 'src/prebid.js'; import {resetAuctionState} from 'src/auction.js'; -import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {createBid} from '../../../src/bidfactory.js'; + var assert = require('chai').assert; var expect = require('chai').expect; @@ -1468,6 +1467,7 @@ describe('Unit: Prebid Module', function () { after(function () { clock.restore(); }); + let bidsBackHandlerStub = sinon.stub(); const BIDDER_CODE = 'sampleBidder'; let bids = [{ @@ -1504,12 +1504,11 @@ describe('Unit: Prebid Module', function () { 'start': 1000 }]; - let spec, indexStub, auction, completeAuction; - beforeEach(function () { logMessageSpy = sinon.spy(utils, 'logMessage'); makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); makeRequestsStub.returns(bidRequests); + adUnits = [{ code: 'adUnit-code', mediaTypes: { @@ -1517,53 +1516,45 @@ describe('Unit: Prebid Module', function () { sizes: [[300, 250]] } }, - transactionId: 'mock-tid', bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }]; - indexStub = sinon.stub(auctionManager, 'index'); - indexStub.get(() => stubAuctionIndex({adUnits, bidRequests})) - sinon.stub(adapterManager, 'callBids').callsFake((_, bidrequests, addBidResponse, adapterDone) => { - completeAuction = (bidsReceived) => { - bidsReceived.forEach((bid) => addBidResponse(bid.adUnitCode, Object.assign(createBid(), bid))); - bidRequests.forEach((req) => adapterDone.call(req)); - } - }) - const origNewAuction = auctionModule.newAuction; - sinon.stub(auctionModule, 'newAuction').callsFake(function (opts) { - auction = origNewAuction(opts); - return auction; - }) - spec = { + let adUnitCodes = ['adUnit-code']; + let auction = auctionModule.newAuction({ + adUnits, + adUnitCodes, + callback: bidsBackHandlerStub, + cbTimeout: 2000 + }); + let createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + }); + + afterEach(function () { + clock.restore(); + adapterManager.makeBidRequests.restore(); + auctionModule.newAuction.restore(); + utils.logMessage.restore(); + }); + + it('should execute callback after timeout', function () { + let spec = { code: BIDDER_CODE, isBidRequestValid: sinon.stub(), buildRequests: sinon.stub(), interpretResponse: sinon.stub(), getUserSyncs: sinon.stub(), - onTimeout: sinon.stub(), - onSetTargeting: sinon.stub(), + onTimeout: sinon.stub() }; registerBidder(spec); spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); spec.isBidRequestValid.returns(true); spec.interpretResponse.returns(bids); - }); - - afterEach(function () { - clock.restore(); - adapterManager.makeBidRequests.restore(); - adapterManager.callBids.restore(); - indexStub.restore(); - auction.getBidsReceived = () => []; - auctionModule.newAuction.restore(); - utils.logMessage.restore(); - }); - it('should execute callback after timeout', function () { let requestObj = { - bidsBackHandler: sinon.stub(), + bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach timeout: 2000, adUnits: adUnits }; @@ -1576,13 +1567,26 @@ describe('Unit: Prebid Module', function () { clock.tick(1); assert.ok(logMessageSpy.calledWith(sinon.match(re)), 'executeCallback called'); - expect(requestObj.bidsBackHandler.getCall(0).args[1]).to.equal(true, + expect(bidsBackHandlerStub.getCall(0).args[1]).to.equal(true, 'bidsBackHandler should be called with timedOut=true'); sinon.assert.called(spec.onTimeout); }); - it('should execute `onSetTargeting` after setTargetingForGPTAsync', function () { + it('should execute callback after setTargeting', function () { + let spec = { + code: BIDDER_CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + onSetTargeting: sinon.stub() + }; + + registerBidder(spec); + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bids); + const bidId = 1; const auctionId = 1; let adResponse = Object.assign({ @@ -1591,7 +1595,6 @@ describe('Unit: Prebid Module', function () { width: 300, height: 250, adUnitCode: bidRequests[0].bids[0].adUnitCode, - transactionId: 'mock-tid', adserverTargeting: { 'hb_bidder': BIDDER_CODE, 'hb_adid': bidId, @@ -1600,77 +1603,20 @@ describe('Unit: Prebid Module', function () { }, bidder: bids[0].bidderCode, }, bids[0]); + auction.getBidsReceived = function() { return [adResponse]; } + auction.getAuctionId = () => auctionId; let requestObj = { - bidsBackHandler: null, + bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach timeout: 2000, adUnits: adUnits }; $$PREBID_GLOBAL$$.requestBids(requestObj); - completeAuction([adResponse]); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); sinon.assert.called(spec.onSetTargeting); }); - - describe('returns a promise that resolves', () => { - Object.entries({ - 'immediately, without bidsBackHandler': (req) => $$PREBID_GLOBAL$$.requestBids(req), - 'after bidsBackHandler': (() => { - const bidsBackHandler = sinon.stub(); - return function (req) { - return $$PREBID_GLOBAL$$.requestBids({...req, bidsBackHandler}).then(({bids, timedOut, auctionId}) => { - sinon.assert.calledWith(bidsBackHandler, bids, timedOut, auctionId); - return {bids, timedOut, auctionId}; - }) - } - })(), - 'after a bidsBackHandler that throws': (req) => $$PREBID_GLOBAL$$.requestBids({...req, bidsBackHandler: () => { throw new Error() }}) - }).forEach(([t, requestBids]) => { - describe(t, () => { - it('with no args, when no adUnits are defined', () => { - return requestBids({}).then((res) => { - expect(res).to.eql({ - bids: undefined, - timedOut: undefined, - auctionId: undefined - }); - }); - }); - - it('on timeout', (done) => { - requestBids({ - auctionId: 'mock-auctionId', - adUnits, - timeout: 10 - }).then(({timedOut, bids, auctionId}) => { - expect(timedOut).to.be.true; - expect(bids).to.eql({}); - expect(auctionId).to.eql('mock-auctionId'); - done(); - }); - clock.tick(12); - }); - - it('with auction result', (done) => { - const bid = { - bidder: 'mock-bidder', - adUnitCode: adUnits[0].code, - transactionId: adUnits[0].transactionId - } - requestBids({ - adUnits, - }).then(({bids}) => { - sinon.assert.match(bids[bid.adUnitCode].bids[0], bid) - done(); - }); - completeAuction([bid]); - }) - }) - }) - }) - it('should transfer ttlBuffer to adUnit.ttlBuffer', () => { $$PREBID_GLOBAL$$.requestBids({ ttlBuffer: 123,