From bb4bdbb58d84567ab5b8cb5dc63fabf1589ab995 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 28 Mar 2024 12:54:37 -0700 Subject: [PATCH 1/3] core: allow bid adapters to return null fledgeAuctionConfigs --- src/adapters/bidderFactory.js | 18 ++++++++----- test/spec/unit/core/bidderFactory_spec.js | 31 +++++++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 4f9237fc8d3..70172869e4b 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -422,15 +422,21 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe return; } - let bids; - // Extract additional data from a structured {BidderAuctionResponse} response - if (response && isArray(response.fledgeAuctionConfigs)) { - response.fledgeAuctionConfigs.forEach(onPaapi); - bids = response.bids; + // adapters can reply with: + // a single bid + // an array of bids + // an object with {bids: [*], fledgeAuctionConfigs: [*]} + + const RESPONSE_PROPS = ['bids', 'fledgeAuctionConfigs']; + let bids, paapiConfigs; + if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { + [bids, paapiConfigs] = RESPONSE_PROPS.map(k => response[k]); } else { bids = response; } - + if (isArray(paapiConfigs)) { + paapiConfigs.forEach(onPaapi); + } if (bids) { if (isArray(bids)) { bids.forEach(addBid); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index aba64733f90..232b4689e33 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1462,6 +1462,26 @@ describe('bidderFactory', () => { foo: 'bar' } } + + it('should unwrap bids', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); + }); + + it('does not unwrap bids from a bid that happens to have a "bids" property', () => { + const bidder = newBidder(spec); + const bid = Object.assign({ + bids: ['a', 'b'] + }, bids[0]); + spec.interpretResponse.returns(bid); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); + }) + describe('when response has FLEDGE auction config', function() { let fledgeStub; @@ -1481,17 +1501,6 @@ describe('bidderFactory', () => { fledgeStub = sinon.stub(); }); - it('should unwrap bids', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }); - it('should call fledgeManager with FLEDGE configs', function() { const bidder = newBidder(spec); spec.interpretResponse.returns({ From a514701a8b8d69443c7958ced41357c8f84b3784 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 2 Apr 2024 09:41:13 -0700 Subject: [PATCH 2/3] Accept paapiAuctionConfigs from adapters --- src/adapters/bidderFactory.js | 22 ++++++-- test/spec/unit/core/bidderFactory_spec.js | 69 +++++++++++++---------- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 70172869e4b..337ae47f338 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -89,8 +89,8 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ /** * @typedef {object} BidderAuctionResponse An object encapsulating an adapter response for current Auction * - * @property {Array} bids Contextual bids returned by this adapter, if any - * @property {object|null} fledgeAuctionConfigs Optional FLEDGE response, as a map of impid -> auction_config + * @property {Array} bids? Contextual bids returned by this adapter, if any + * @property {Array<{bidId: String, config: {}}>} paapiAuctionConfigs? Array of paapi auction configs, each scoped to a particular bidId */ /** @@ -361,6 +361,18 @@ export function newBidder(spec) { } } +// Transition from 'fledge' to 'paapi' +// TODO: remove this in prebid 9 +const PAAPI_RESPONSE_PROPS = ['paapiAuctionConfigs', 'fledgeAuctionConfigs']; +const RESPONSE_PROPS = ['bids'].concat(PAAPI_RESPONSE_PROPS); +function getPaapiConfigs(adapterResponse) { + const [paapi, fledge] = PAAPI_RESPONSE_PROPS.map(prop => adapterResponse[prop]); + if (paapi != null && fledge != null) { + throw new Error(`Adapter response should use ${PAAPI_RESPONSE_PROPS[0]} over ${PAAPI_RESPONSE_PROPS[1]}, not both`); + } + return paapi ?? fledge; +} + /** * Run a set of bid requests - that entails converting them to HTTP requests, sending * them over the network, and parsing the responses. @@ -425,12 +437,12 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe // adapters can reply with: // a single bid // an array of bids - // an object with {bids: [*], fledgeAuctionConfigs: [*]} + // a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]}) - const RESPONSE_PROPS = ['bids', 'fledgeAuctionConfigs']; let bids, paapiConfigs; if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { - [bids, paapiConfigs] = RESPONSE_PROPS.map(k => response[k]); + bids = response.bids; + paapiConfigs = getPaapiConfigs(response); } else { bids = response; } diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 232b4689e33..270a64eaff4 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1456,7 +1456,7 @@ describe('bidderFactory', () => { transactionId: 'au', }] }; - const fledgeAuctionConfig = { + const paapiConfig = { bidId: '1', config: { foo: 'bar' @@ -1482,50 +1482,59 @@ describe('bidderFactory', () => { sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); }) - describe('when response has FLEDGE auction config', function() { - let fledgeStub; + describe('when response has PAAPI auction config', function() { + let paapiStub; - function fledgeHook(next, ...args) { - fledgeStub(...args); + function paapiHook(next, ...args) { + paapiStub(...args); } before(() => { - addComponentAuction.before(fledgeHook); + addComponentAuction.before(paapiHook); }); after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); + addComponentAuction.getHooks({hook: paapiHook}).remove(); }) beforeEach(function () { - fledgeStub = sinon.stub(); + paapiStub = sinon.stub(); }); - it('should call fledgeManager with FLEDGE configs', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + ['fledgeAuctionConfigs', 'paapiAuctionConfigs'].forEach(paapiProp => { + describe(`using ${paapiProp}`, () => { + it('should call paapi hook with PAAPI configs', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(fledgeStub.calledOnce).to.equal(true); - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }) + expect(paapiStub.calledOnce).to.equal(true); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }) - it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: [], - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + Object.entries({ + 'missing': undefined, + 'an empty array': [] + }).forEach(([t, bids]) => { + it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(fledgeStub.calledOnce).to.be.true; - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(paapiStub.calledOnce).to.be.true; + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(false); + }) + }) + }) }) }) }) From 6406e963b2b83872dc27febc46fbdcb736b806c0 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 3 Apr 2024 08:38:42 -0700 Subject: [PATCH 3/3] add test case --- test/spec/unit/core/bidderFactory_spec.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 270a64eaff4..5fe5a1accfc 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1501,7 +1501,17 @@ describe('bidderFactory', () => { paapiStub = sinon.stub(); }); - ['fledgeAuctionConfigs', 'paapiAuctionConfigs'].forEach(paapiProp => { + const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapiAuctionConfigs']; + + it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { + const bidder = newBidder(spec); + spec.interpretResponse.returns(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) + expect(() => { + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + }).to.throw; + }) + + PAAPI_PROPS.forEach(paapiProp => { describe(`using ${paapiProp}`, () => { it('should call paapi hook with PAAPI configs', function() { const bidder = newBidder(spec);