Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: allow bid adapters to return null fledgeAuctionConfigs #11271

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niiiiice


/**
* 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a test for prefers 'paapiAuctionConfigs' over 'fledgeAuctionConfigs' if both defined?

Prob not a real life scenario though so maybe not!


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