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

Debugging module: add PAAPI support #11240

Merged
merged 8 commits into from
Mar 22, 2024
27 changes: 23 additions & 4 deletions modules/debugging/bidInterceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ Object.assign(BidInterceptor.prototype, {
return {
no: ruleNo,
match: this.matcher(ruleDef.when, ruleNo),
replace: this.replacer(ruleDef.then || {}, ruleNo),
replace: this.replacer(ruleDef.then, ruleNo),
options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options),
paapi: this.paapiReplacer(ruleDef.paapi || [], ruleNo)
}
},
/**
Expand Down Expand Up @@ -114,6 +115,10 @@ Object.assign(BidInterceptor.prototype, {
* @return {ReplacerFn}
*/
replacer(replDef, ruleNo) {
if (replDef === null) {
return () => null
}
replDef = replDef || {};
let replFn;
if (typeof replDef === 'function') {
replFn = ({args}) => replDef(...args);
Expand Down Expand Up @@ -145,6 +150,17 @@ Object.assign(BidInterceptor.prototype, {
return response;
}
},

paapiReplacer(paapiDef, ruleNo) {
if (Array.isArray(paapiDef)) {
return () => paapiDef;
} else if (typeof paapiDef === 'function') {
return paapiDef
} else {
this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`);
}
},

responseDefaults(bid) {
return {
requestId: bid.bidId,
Expand Down Expand Up @@ -198,11 +214,12 @@ Object.assign(BidInterceptor.prototype, {
* @param {{}[]} bids?
* @param {BidRequest} bidRequest
* @param {function(*)} addBid called once for each mock response
* @param addPaapiConfig called once for each mock PAAPI config
* @param {function()} done called once after all mock responses have been run through `addBid`
* @returns {{bids: {}[], bidRequest: {}} remaining bids that did not match any rule (this applies also to
* bidRequest.bids)
*/
intercept({bids, bidRequest, addBid, done}) {
intercept({bids, bidRequest, addBid, addPaapiConfig, done}) {
if (bids == null) {
bids = bidRequest.bids;
}
Expand All @@ -211,10 +228,12 @@ Object.assign(BidInterceptor.prototype, {
const callDone = delayExecution(done, matches.length);
matches.forEach((match) => {
const mockResponse = match.rule.replace(match.bid, bidRequest);
const mockPaapi = match.rule.paapi(match.bid, bidRequest);
const delay = match.rule.options.delay;
this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse)
this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response, PAAPI configs:`, match.bid, mockResponse, mockPaapi)
this.setTimeout(() => {
addBid(mockResponse, match.bid);
mockResponse && addBid(mockResponse, match.bid);
mockPaapi.forEach(cfg => addPaapiConfig(cfg, match.bid, bidRequest));
callDone();
}, delay)
});
Expand Down
8 changes: 7 additions & 1 deletion modules/debugging/debugging.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ function registerBidInterceptor(getHookFn, interceptor) {

export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) {
const done = delayExecution(cbs.onCompletion, 2);
({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done}));
({bids, bidRequest} = interceptBids({
bids,
bidRequest,
addBid: cbs.onBid,
addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, config}),
done
}));
if (bids.length === 0) {
done();
} else {
Expand Down
17 changes: 15 additions & 2 deletions modules/debugging/pbsInterceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export function makePbsInterceptor({createBid}) {
return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, {
onResponse,
onError,
onBid
onBid,
onFledge,
}) {
let responseArgs;
const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1)
Expand All @@ -20,7 +21,19 @@ export function makePbsInterceptor({createBid}) {
})
}
bidRequests = bidRequests
.map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest)
.map((req) => interceptBids({
bidRequest: req,
addBid,
addPaapiConfig(config, bidRequest, bidderRequest) {
onFledge({
adUnitCode: bidRequest.adUnitCode,
ortb2: bidderRequest.ortb2,
ortb2Imp: bidRequest.ortb2Imp,
config
})
},
done
}).bidRequest)
.filter((req) => req.bids.length > 0)

if (bidRequests.length > 0) {
Expand Down
20 changes: 9 additions & 11 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,13 @@ export function newBidder(spec) {
onTimelyResponse(spec.code);
responses.push(resp)
},
onFledgeAuctionConfigs: (fledgeAuctionConfigs) => {
fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => {
const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId];
if (bidRequest) {
addComponentAuction(bidRequest, fledgeAuctionConfig.config);
} else {
logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig);
}
});
onPaapi: (paapiConfig) => {
const bidRequest = bidRequestMap[paapiConfig.bidId];
if (bidRequest) {
addComponentAuction(bidRequest, paapiConfig.config);
} else {
logWarn('Received fledge auction configuration for an unknown bidId', paapiConfig);
}
},
// If the server responds with an error, there's not much we can do beside logging.
onError: (errorMessage, error) => {
Expand Down Expand Up @@ -378,7 +376,7 @@ export function newBidder(spec) {
* @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse
* @param onCompletion {function()} invoked once when all bid requests have been processed
*/
export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onFledgeAuctionConfigs, onError, onBid, onCompletion}) {
export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) {
const metrics = adapterMetrics(bidderRequest);
onCompletion = metrics.startTiming('total').stopBefore(onCompletion);

Expand Down Expand Up @@ -427,7 +425,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe
let bids;
// Extract additional data from a structured {BidderAuctionResponse} response
if (response && isArray(response.fledgeAuctionConfigs)) {
onFledgeAuctionConfigs(response.fledgeAuctionConfigs);
response.fledgeAuctionConfigs.forEach(onPaapi);
bids = response.bids;
} else {
bids = response;
Expand Down
50 changes: 46 additions & 4 deletions test/spec/modules/debugging_mod_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ describe('bid interceptor', () => {
});

describe('rule', () => {
function matchingRule({replace, options}) {
setRules({when: {}, then: replace, options: options});
function matchingRule({replace, options, paapi}) {
setRules({when: {}, then: replace, options: options, paapi});
return interceptor.match({});
}

Expand Down Expand Up @@ -164,6 +164,24 @@ describe('bid interceptor', () => {
});
});

describe('paapi', () => {
it('should accept literals', () => {
const mockConfig = [
{paapi: 1},
{paapi: 2}
]
const paapi = matchingRule({paapi: mockConfig}).paapi({});
expect(paapi).to.eql(mockConfig);
});

it('should accept a function and pass extra args to it', () => {
const paapiDef = sinon.stub();
const args = [{}, {}, {}];
matchingRule({paapi: paapiDef}).paapi(...args);
expect(paapiDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true;
})
})

describe('.options', () => {
it('should include default rule options', () => {
const optDef = {someOption: 'value'};
Expand All @@ -181,16 +199,17 @@ describe('bid interceptor', () => {
});

describe('intercept()', () => {
let done, addBid;
let done, addBid, addPaapiConfig;

function intercept(args = {}) {
const bidRequest = {bids: args.bids || []};
return interceptor.intercept(Object.assign({bidRequest, done, addBid}, args));
return interceptor.intercept(Object.assign({bidRequest, done, addBid, addPaapiConfig}, args));
}

beforeEach(() => {
done = sinon.spy();
addBid = sinon.spy();
addPaapiConfig = sinon.spy();
});

describe('on no match', () => {
Expand Down Expand Up @@ -253,6 +272,29 @@ describe('bid interceptor', () => {
});
});

it('should call addPaapiConfigs when provided', () => {
const mockPaapiConfigs = [
{paapi: 1},
{paapi: 2}
]
setRules({
when: {id: 2},
paapi: mockPaapiConfigs,
});
intercept({bidRequest: REQUEST});
expect(addPaapiConfig.callCount).to.eql(2);
mockPaapiConfigs.forEach(cfg => sinon.assert.calledWith(addPaapiConfig, cfg))
})

it('should not call onBid when then is null', () => {
setRules({
when: {id: 2},
then: null
});
intercept({bidRequest: REQUEST});
sinon.assert.notCalled(addBid);
})

it('should call done()', () => {
intercept({bidRequest: REQUEST});
expect(done.calledOnce).to.be.true;
Expand Down