Skip to content

Commit

Permalink
core: allow bid adapters to return null fledgeAuctionConfigs (prebid#…
Browse files Browse the repository at this point in the history
…11271)

* core: allow bid adapters to return null fledgeAuctionConfigs

* Accept paapiAuctionConfigs from adapters

* add test case
  • Loading branch information
dgirardi authored and mefjush committed Apr 8, 2024
1 parent ec847a3 commit 4db7f72
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 46 deletions.
32 changes: 25 additions & 7 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bid>} 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<Bid>} 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
*/

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -422,15 +434,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);
// adapters can reply with:
// a single bid
// an array of bids
// a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]})

let bids, paapiConfigs;
if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) {
bids = response.bids;
paapiConfigs = getPaapiConfigs(response);
} else {
bids = response;
}

if (isArray(paapiConfigs)) {
paapiConfigs.forEach(onPaapi);
}
if (bids) {
if (isArray(bids)) {
bids.forEach(addBid);
Expand Down
106 changes: 67 additions & 39 deletions test/spec/unit/core/bidderFactory_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1456,67 +1456,95 @@ describe('bidderFactory', () => {
transactionId: 'au',
}]
};
const fledgeAuctionConfig = {
const paapiConfig = {
bidId: '1',
config: {
foo: 'bar'
}
}
describe('when response has FLEDGE auction config', function() {
let fledgeStub;

function fledgeHook(next, ...args) {
fledgeStub(...args);
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 PAAPI auction config', function() {
let paapiStub;

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 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');
});
const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapiAuctionConfigs'];

it('should call fledgeManager with FLEDGE configs', function() {
it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => {
const bidder = newBidder(spec);
spec.interpretResponse.returns({
bids: bids,
fledgeAuctionConfigs: [fledgeAuctionConfig]
});
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');
spec.interpretResponse.returns(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]])))
expect(() => {
bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback);
}).to.throw;
})

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);
PAAPI_PROPS.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.be.true;
sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config);
expect(addBidResponseStub.calledOnce).to.equal(false);
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');
})

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(paapiStub.calledOnce).to.be.true;
sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config);
expect(addBidResponseStub.calledOnce).to.equal(false);
})
})
})
})
})
})
Expand Down

0 comments on commit 4db7f72

Please sign in to comment.