mk9-prebid
Version:
Header Bidding Management Library
1,449 lines (1,265 loc) • 45.8 kB
JavaScript
import {
getKeyValueTargetingPairs,
auctionCallbacks,
AUCTION_COMPLETED,
adjustBids,
getMediaTypeGranularity,
} from 'src/auction.js';
import CONSTANTS from 'src/constants.json';
import * as auctionModule from 'src/auction.js';
import { registerBidder } from 'src/adapters/bidderFactory.js';
import { createBid } from 'src/bidfactory.js';
import { config } from 'src/config.js';
import * as store from 'src/videoCache.js';
import * as ajaxLib from 'src/ajax.js';
import find from 'core-js-pure/features/array/find.js';
import { server } from 'test/mocks/xhr.js';
var assert = require('assert');
/* use this method to test individual files instead of the whole prebid.js project */
// TODO refactor to use the spec files
var utils = require('../../src/utils');
var fixtures = require('../fixtures/fixtures');
var adapterManager = require('src/adapterManager').default;
var events = require('src/events');
const BIDDER_CODE = 'sampleBidder';
const BIDDER_CODE1 = 'sampleBidder1';
const ADUNIT_CODE = 'adUnit-code';
const ADUNIT_CODE1 = 'adUnit-code-1';
/**
* @param {Object} [opts]
* @returns {Bid}
*/
function mockBid(opts) {
let bidderCode = opts && opts.bidderCode;
return {
'ad': 'creative',
'cpm': '1.99',
'width': 300,
'height': 250,
'bidderCode': bidderCode || BIDDER_CODE,
'requestId': utils.getUniqueIdentifierStr(),
'creativeId': 'id',
'currency': 'USD',
'netRevenue': true,
'ttl': 360,
getSize: () => '300x250'
};
}
/**
* @param {Bid} bid
* @param {Object} [opts]
* @returns {BidRequest}
*/
function mockBidRequest(bid, opts) {
if (!bid) {
throw new Error('bid required');
}
let bidderCode = opts && opts.bidderCode;
let adUnitCode = opts && opts.adUnitCode;
let defaultMediaType = {
banner: {
sizes: [[300, 250], [300, 600]]
}
}
let mediaType = (opts && opts.mediaType) ? opts.mediaType : defaultMediaType;
let requestId = utils.getUniqueIdentifierStr();
return {
'bidderCode': bidderCode || bid.bidderCode,
'auctionId': '20882439e3238c',
'bidderRequestId': requestId,
'bids': [
{
'bidder': bidderCode || bid.bidderCode,
'params': {
'placementId': 'id'
},
'adUnitCode': adUnitCode || ADUNIT_CODE,
'sizes': [[300, 250], [300, 600]],
'bidId': bid.requestId,
'bidderRequestId': requestId,
'auctionId': '20882439e3238c',
'mediaTypes': mediaType
}
],
'auctionStart': 1505250713622,
'timeout': 3000
};
}
function mockBidder(bidderCode, bids) {
let spec = {
code: bidderCode,
isBidRequestValid: sinon.stub(),
buildRequests: sinon.stub(),
interpretResponse: sinon.stub(),
getUserSyncs: sinon.stub()
};
spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]);
spec.isBidRequestValid.returns(true);
spec.interpretResponse.returns(bids);
return spec;
}
const TEST_BIDS = [mockBid()];
const TEST_BID_REQS = TEST_BIDS.map(mockBidRequest);
function mockAjaxBuilder() {
return function(url, callback) {
const fakeResponse = sinon.stub();
fakeResponse.returns('headerContent');
callback.success('response body', { getResponseHeader: fakeResponse });
};
}
describe('auctionmanager.js', function () {
describe('getKeyValueTargetingPairs', function () {
const DEFAULT_BID = {
cpm: 5.578,
pbLg: 5.50,
pbMg: 5.50,
pbHg: 5.57,
pbAg: 5.50,
height: 300,
width: 250,
getSize() {
return this.height + 'x' + this.width;
},
adUnitCode: '12345',
bidderCode: 'appnexus',
adId: '1adId',
source: 'client',
mediaType: 'banner',
meta: {
advertiserDomains: ['adomain']
}
};
/* return the expected response for a given bid, filter by keys if given */
function getDefaultExpected(bid, keys) {
var expected = {};
expected[ CONSTANTS.TARGETING_KEYS.BIDDER ] = bid.bidderCode;
expected[ CONSTANTS.TARGETING_KEYS.AD_ID ] = bid.adId;
expected[ CONSTANTS.TARGETING_KEYS.PRICE_BUCKET ] = bid.pbMg;
expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize();
expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source;
expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType;
expected[ CONSTANTS.TARGETING_KEYS.ADOMAIN ] = bid.meta.advertiserDomains[0];
if (bid.mediaType === 'video') {
expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey;
expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey;
expected[ CONSTANTS.TARGETING_KEYS.CACHE_HOST ] = 'prebid.adnxs.com';
}
if (!keys) {
return expected;
}
return keys.reduce((map, key) => {
map[key] = expected[key];
return map;
}, {});
}
var bid = {};
before(function () {
bid = Object.assign({}, DEFAULT_BID);
});
it('No bidder level configuration defined - default', function () {
$$PREBID_GLOBAL$$.bidderSettings = {};
let expected = getDefaultExpected(bid);
// remove hb_cache_host from expected
delete expected.hb_cache_host;
let response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('No bidder level configuration defined - default for video', function () {
config.setConfig({
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
}
});
$$PREBID_GLOBAL$$.bidderSettings = {};
let videoBid = utils.deepClone(bid);
videoBid.mediaType = 'video';
videoBid.videoCacheKey = 'abc123def';
let expected = getDefaultExpected(videoBid);
let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid);
assert.deepEqual(response, expected);
});
it('Custom configuration for all bidders', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
standard: {
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return bidResponse.pbHg;
}
}, {
key: CONSTANTS.TARGETING_KEYS.SIZE,
val: function (bidResponse) {
return bidResponse.size;
}
},
{
key: CONSTANTS.TARGETING_KEYS.SOURCE,
val: function (bidResponse) {
return bidResponse.source;
}
},
{
key: CONSTANTS.TARGETING_KEYS.FORMAT,
val: function (bidResponse) {
return bidResponse.mediaType;
}
},
{
key: CONSTANTS.TARGETING_KEYS.ADOMAIN,
val: function (bidResponse) {
return bidResponse.meta.advertiserDomains[0];
}
}
]
}
};
var expected = getDefaultExpected(bid);
expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg;
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('Custom configuration for all bidders with video bid', function () {
config.setConfig({
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
}
});
let videoBid = utils.deepClone(bid);
videoBid.mediaType = 'video';
videoBid.videoCacheKey = 'abc123def';
$$PREBID_GLOBAL$$.bidderSettings =
{
standard: {
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
return bidResponse.pbMg;
}
}, {
key: CONSTANTS.TARGETING_KEYS.SIZE,
val: function (bidResponse) {
return bidResponse.size;
}
},
{
key: CONSTANTS.TARGETING_KEYS.SOURCE,
val: function (bidResponse) {
return bidResponse.source;
}
},
{
key: CONSTANTS.TARGETING_KEYS.FORMAT,
val: function (bidResponse) {
return bidResponse.mediaType;
}
},
{
key: CONSTANTS.TARGETING_KEYS.UUID,
val: function (bidResponse) {
return bidResponse.videoCacheKey;
}
},
{
key: CONSTANTS.TARGETING_KEYS.CACHE_ID,
val: function (bidResponse) {
return bidResponse.videoCacheKey;
}
},
{
key: CONSTANTS.TARGETING_KEYS.ADOMAIN,
val: function (bidResponse) {
return bidResponse.meta.advertiserDomains[0];
}
}
]
}
};
let expected = getDefaultExpected(videoBid);
let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid);
assert.deepEqual(response, expected);
});
it('Custom configuration for one bidder', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
appnexus: {
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return bidResponse.pbHg;
}
}, {
key: CONSTANTS.TARGETING_KEYS.SIZE,
val: function (bidResponse) {
return bidResponse.size;
}
}
]
}
};
var expected = getDefaultExpected(bid);
expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg;
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('Custom configuration for one bidder - not matched', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
nonExistentBidder: {
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return bidResponse.pbHg;
}
}, {
key: CONSTANTS.TARGETING_KEYS.SIZE,
val: function (bidResponse) {
return bidResponse.size;
}
}
]
}
};
var expected = getDefaultExpected(bid);
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
appnexus: {
bidCpmAdjustment: function (bidCpm) {
return bidCpm * 0.7;
},
},
standard: {
bidCpmAdjustment: function (bidCpm) {
return 200;
},
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return 10.00;
}
}
]
}
};
var expected = getDefaultExpected(bid, [CONSTANTS.TARGETING_KEYS.BIDDER, CONSTANTS.TARGETING_KEYS.AD_ID]);
expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 10.0;
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('Standard bidCpmAdjustment changes the bid of any bidder', function () {
const bid = Object.assign({},
createBid(2),
fixtures.getBidResponses()[5]
);
assert.equal(bid.cpm, 0.5);
$$PREBID_GLOBAL$$.bidderSettings =
{
standard: {
bidCpmAdjustment: function (bidCpm) {
return bidCpm * 0.5;
}
}
};
adjustBids(bid)
assert.equal(bid.cpm, 0.25);
});
it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
appnexus: {
bidCpmAdjustment: function (bidCpm) {
return bidCpm * 0.7;
},
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return 15.00;
}
}
]
},
standard: {
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
// change default here
return 10.00;
},
},
{
key: CONSTANTS.TARGETING_KEYS.SIZE,
val: function (bidResponse) {
return bidResponse.size;
}
}
]
}
};
var expected = getDefaultExpected(bid, [CONSTANTS.TARGETING_KEYS.BIDDER, CONSTANTS.TARGETING_KEYS.AD_ID, CONSTANTS.TARGETING_KEYS.SIZE]);
expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 15.0;
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
it('sendStandardTargeting=false, and inherit custom', function () {
$$PREBID_GLOBAL$$.bidderSettings =
{
appnexus: {
sendStandardTargeting: false,
adserverTargeting: [
{
key: CONSTANTS.TARGETING_KEYS.BIDDER,
val: function (bidResponse) {
return bidResponse.bidderCode;
}
}, {
key: CONSTANTS.TARGETING_KEYS.AD_ID,
val: function (bidResponse) {
return bidResponse.adId;
}
}, {
key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET,
val: function (bidResponse) {
return bidResponse.pbHg;
}
}
]
}
};
var expected = getDefaultExpected(bid);
expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 5.57;
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
assert.equal(bid.sendStandardTargeting, false);
});
it('suppressEmptyKeys=true', function() {
$$PREBID_GLOBAL$$.bidderSettings =
{
standard: {
suppressEmptyKeys: true,
adserverTargeting: [
{
key: 'aKeyWithAValue',
val: 42
},
{
key: 'aKeyWithAnEmptyValue',
val: ''
}
]
}
};
var expected = {
'aKeyWithAValue': 42
};
var response = getKeyValueTargetingPairs(bid.bidderCode, bid);
assert.deepEqual(response, expected);
});
});
describe('adjustBids', function () {
it('should adjust bids if greater than zero and pass copy of bid object', function () {
const bid = Object.assign({},
createBid(2),
fixtures.getBidResponses()[5]
);
assert.equal(bid.cpm, 0.5);
$$PREBID_GLOBAL$$.bidderSettings =
{
brealtime: {
bidCpmAdjustment: function (bidCpm, bidObj) {
assert.deepEqual(bidObj, bid);
if (bidObj.adUnitCode === 'negative') {
return bidCpm * -0.5;
}
if (bidObj.adUnitCode === 'zero') {
return 0;
}
return bidCpm * 0.5;
},
},
standard: {
adserverTargeting: [
]
}
};
// negative
bid.adUnitCode = 'negative';
adjustBids(bid)
assert.equal(bid.cpm, 0.5);
// positive
bid.adUnitCode = 'normal';
adjustBids(bid)
assert.equal(bid.cpm, 0.25);
// zero
bid.adUnitCode = 'zero';
adjustBids(bid)
assert.equal(bid.cpm, 0);
// reset bidderSettings so we don't mess up further tests
$$PREBID_GLOBAL$$.bidderSettings = {};
});
});
describe('addBidResponse', function () {
let createAuctionStub;
let adUnits;
let adUnitCodes;
let spec;
let auction;
let ajaxStub;
let bids = TEST_BIDS;
let makeRequestsStub;
before(function () {
makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests');
});
after(function () {
adapterManager.makeBidRequests.restore();
});
describe('when auction timeout is 3000', function () {
before(function () {
ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder);
makeRequestsStub.returns(TEST_BID_REQS);
});
beforeEach(function () {
adUnits = [{
code: ADUNIT_CODE,
bids: [
{bidder: BIDDER_CODE, params: {placementId: 'id'}},
]
}];
adUnitCodes = [ADUNIT_CODE];
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000});
createAuctionStub = sinon.stub(auctionModule, 'newAuction');
createAuctionStub.returns(auction);
spec = mockBidder(BIDDER_CODE, bids);
registerBidder(spec);
});
afterEach(function () {
auctionModule.newAuction.restore();
});
after(function () {
ajaxStub.restore();
});
function checkPbDg(cpm, expected, msg) {
return function() {
bids[0].cpm = cpm;
auction.callBids();
let registeredBid = auction.getBidsReceived().pop();
assert.equal(registeredBid.pbDg, expected, msg);
};
};
it('should return proper price bucket increments for dense mode when cpm is in range 0-3',
checkPbDg('1.99', '1.99', '0 - 3 hits at to 1 cent increment'));
it('should return proper price bucket increments for dense mode when cpm is in range 3-8',
checkPbDg('4.39', '4.35', '3 - 8 hits at 5 cent increment'));
it('should return proper price bucket increments for dense mode when cpm is in range 8-20',
checkPbDg('19.99', '19.50', '8 - 20 hits at 50 cent increment'));
it('should return proper price bucket increments for dense mode when cpm is 20+',
checkPbDg('73.07', '20.00', '20+ caps at 20.00'));
it('should place dealIds in adserver targeting', function () {
bids[0].dealId = 'test deal';
auction.callBids();
let registeredBid = auction.getBidsReceived().pop();
assert.equal(registeredBid.adserverTargeting[CONSTANTS.TARGETING_KEYS.DEAL], 'test deal', 'dealId placed in adserverTargeting');
});
it('should pass through default adserverTargeting sent from adapter', function () {
bids[0].adserverTargeting = {};
bids[0].adserverTargeting.extra = 'stuff';
auction.callBids();
let registeredBid = auction.getBidsReceived().pop();
assert.equal(registeredBid.adserverTargeting[CONSTANTS.TARGETING_KEYS.BIDDER], BIDDER_CODE);
assert.equal(registeredBid.adserverTargeting.extra, 'stuff');
});
it('installs publisher-defined renderers on bids', function () {
let renderer = {
url: 'renderer.js',
render: (bid) => bid
};
let bidRequests = [Object.assign({}, TEST_BID_REQS[0])];
bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]);
makeRequestsStub.returns(bidRequests);
let bids1 = Object.assign({},
bids[0],
{
bidderCode: BIDDER_CODE,
mediaType: 'video-outstream',
}
);
spec.interpretResponse.returns(bids1);
auction.callBids();
const addedBid = auction.getBidsReceived().pop();
assert.equal(addedBid.renderer.url, 'renderer.js');
});
it('installs publisher-defined backup renderers on bids', function () {
let renderer = {
url: 'renderer.js',
backupOnly: true,
render: (bid) => bid
};
let bidRequests = [Object.assign({}, TEST_BID_REQS[0])];
bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]);
makeRequestsStub.returns(bidRequests);
let bids1 = Object.assign({},
bids[0],
{
bidderCode: BIDDER_CODE,
mediaType: 'video-outstream',
}
);
spec.interpretResponse.returns(bids1);
auction.callBids();
const addedBid = auction.getBidsReceived().pop();
assert.equal(addedBid.renderer.url, 'renderer.js');
});
it('installs publisher-defined renderers for a media type', function () {
const renderer = {
url: 'videoRenderer.js',
render: (bid) => bid
};
let myBid = mockBid();
let bidRequest = mockBidRequest(myBid);
bidRequest.bids[0] = {
...bidRequest.bids[0],
mediaTypes: {
banner: {
sizes: [[300, 250], [300, 600]]
},
video: {
context: 'outstream',
renderer
}
}
};
makeRequestsStub.returns([bidRequest]);
myBid.mediaType = 'video';
spec.interpretResponse.returns(myBid);
auction.callBids();
const addedBid = auction.getBidsReceived().pop();
assert.equal(addedBid.renderer.url, renderer.url);
});
it('installs bidder-defined renderer when onlyBackup is true in mediaTypes.video options ', function () {
const renderer = {
url: 'videoRenderer.js',
backupOnly: true,
render: (bid) => bid
};
let myBid = mockBid();
let bidRequest = mockBidRequest(myBid);
bidRequest.bids[0] = {
...bidRequest.bids[0],
mediaTypes: {
video: {
context: 'outstream',
renderer
}
}
};
makeRequestsStub.returns([bidRequest]);
myBid.mediaType = 'video';
myBid.renderer = {
url: 'renderer.js',
render: sinon.spy()
};
spec.interpretResponse.returns(myBid);
auction.callBids();
const addedBid = auction.getBidsReceived().pop();
assert.strictEqual(addedBid.renderer.url, myBid.renderer.url);
});
it('bid for a regular unit and a video unit', function() {
let renderer = {
url: 'renderer.js',
render: (bid) => bid
};
// make sure that if the renderer is only on the second ad unit, prebid
// still correctly uses it
let bid = mockBid();
let bidRequests = [mockBidRequest(bid)];
bidRequests[0].bids[1] = Object.assign({
renderer,
bidId: utils.getUniqueIdentifierStr()
}, bidRequests[0].bids[0]);
bidRequests[0].bids[0].adUnitCode = ADUNIT_CODE1;
makeRequestsStub.returns(bidRequests);
// this should correspond with the second bid in the bidReq because of the ad unit code
bid.mediaType = 'video-outstream';
spec.interpretResponse.returns(bid);
auction.callBids();
const addedBid = find(auction.getBidsReceived(), bid => bid.adUnitCode == ADUNIT_CODE);
assert.equal(addedBid.renderer.url, 'renderer.js');
});
});
describe('when auction timeout is 20', function () {
let eventsEmitSpy;
before(function () {
bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })];
let bidRequests = bids.map(bid => mockBidRequest(bid));
makeRequestsStub.returns(bidRequests);
});
beforeEach(function () {
adUnits = [{
code: ADUNIT_CODE,
bids: [
{bidder: BIDDER_CODE, params: {placementId: 'id'}},
]
}];
adUnitCodes = [ADUNIT_CODE];
eventsEmitSpy = sinon.spy(events, 'emit');
});
afterEach(function () {
events.emit.restore();
});
it('should emit BID_TIMEOUT and AUCTION_END for timed out bids', function (done) {
const spec1 = mockBidder(BIDDER_CODE, [bids[0]]);
registerBidder(spec1);
const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]);
registerBidder(spec2);
function respondToRequest(requestIndex) {
server.requests[requestIndex].respond(200, {}, 'response body');
}
function auctionCallback() {
const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0];
const timedOutBids = bidTimeoutCall.args[1];
assert.equal(timedOutBids.length, 1);
assert.equal(timedOutBids[0].bidder, BIDDER_CODE1);
const auctionEndCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.AUCTION_END).getCalls()[0];
const auctionProps = auctionEndCall.args[1];
assert.equal(auctionProps.adUnits, adUnits);
assert.equal(auctionProps.timeout, 20);
assert.equal(auctionProps.auctionStatus, AUCTION_COMPLETED)
done();
}
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20});
auction.callBids();
respondToRequest(0);
});
it('should NOT emit BID_TIMEOUT when all bidders responded in time', function (done) {
const spec1 = mockBidder(BIDDER_CODE, [bids[0]]);
registerBidder(spec1);
const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]);
registerBidder(spec2);
function respondToRequest(requestIndex) {
server.requests[requestIndex].respond(200, {}, 'response body');
}
function auctionCallback() {
assert.ok(eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).notCalled, 'did not emit event BID_TIMEOUT');
done();
}
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20});
auction.callBids();
respondToRequest(0);
respondToRequest(1);
});
it('should NOT emit BID_TIMEOUT for bidders which responded in time but with an empty bid', function (done) {
const spec1 = mockBidder(BIDDER_CODE, []);
registerBidder(spec1);
const spec2 = mockBidder(BIDDER_CODE1, []);
registerBidder(spec2);
function respondToRequest(requestIndex) {
server.requests[requestIndex].respond(200, {}, 'response body');
}
function auctionCallback() {
const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0];
const timedOutBids = bidTimeoutCall.args[1];
assert.equal(timedOutBids.length, 1);
assert.equal(timedOutBids[0].bidder, BIDDER_CODE1);
done();
}
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20});
auction.callBids();
respondToRequest(0);
});
});
});
describe('addBidResponse', function () {
let createAuctionStub;
let adUnits;
let adUnitCodes;
let spec;
let spec1;
let auction;
let ajaxStub;
let bids = TEST_BIDS;
let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })];
before(function () {
let bidRequests = [
mockBidRequest(bids[0]),
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 })
];
let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests');
makeRequestsStub.returns(bidRequests);
ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder);
});
after(function () {
ajaxStub.restore();
adapterManager.makeBidRequests.restore();
});
beforeEach(function () {
adUnits = [{
code: ADUNIT_CODE,
bids: [
{bidder: BIDDER_CODE, params: {placementId: 'id'}},
]
}, {
code: ADUNIT_CODE1,
bids: [
{bidder: BIDDER_CODE1, params: {placementId: 'id'}},
]
}];
adUnitCodes = adUnits.map(({ code }) => code);
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000});
createAuctionStub = sinon.stub(auctionModule, 'newAuction');
createAuctionStub.returns(auction);
spec = mockBidder(BIDDER_CODE, bids);
spec1 = mockBidder(BIDDER_CODE1, bids1);
registerBidder(spec);
registerBidder(spec1);
});
afterEach(function () {
auctionModule.newAuction.restore();
});
it('should not alter bid requestID', function () {
auction.callBids();
const addedBid2 = auction.getBidsReceived().pop();
assert.equal(addedBid2.requestId, bids1[0].requestId);
const addedBid1 = auction.getBidsReceived().pop();
assert.equal(addedBid1.requestId, bids[0].requestId);
});
it('should not add banner bids that have no width or height', function () {
bids1[0].width = undefined;
bids1[0].height = undefined;
auction.callBids();
let length = auction.getBidsReceived().length;
const addedBid2 = auction.getBidsReceived().pop();
assert.notEqual(addedBid2.adId, bids1[0].requestId);
assert.equal(length, 1);
});
it('should run auction after video bids have been cached', function () {
sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]);
sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url');
const bidsCopy = [Object.assign({}, bids[0], { mediaType: 'video' })];
const bids1Copy = [Object.assign({}, bids1[0], { mediaType: 'video' })];
spec.interpretResponse.returns(bidsCopy);
spec1.interpretResponse.returns(bids1Copy);
auction.callBids();
assert.equal(auction.getBidsReceived().length, 2);
assert.equal(auction.getAuctionStatus(), 'completed');
config.getConfig.restore();
store.store.restore();
});
it('runs auction after video responses with multiple bid objects have been cached', function () {
sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]);
sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url');
const bidsCopy = [
Object.assign({}, bids[0], { mediaType: 'video' }),
Object.assign({}, bids[0], { mediaType: 'banner' }),
];
const bids1Copy = [
Object.assign({}, bids1[0], { mediaType: 'video' }),
Object.assign({}, bids1[0], { mediaType: 'video' }),
];
spec.interpretResponse.returns(bidsCopy);
spec1.interpretResponse.returns(bids1Copy);
auction.callBids();
assert.equal(auction.getBidsReceived().length, 4);
assert.equal(auction.getAuctionStatus(), 'completed');
config.getConfig.restore();
store.store.restore();
});
});
describe('addBidRequests', function () {
let createAuctionStub;
let adUnits;
let adUnitCodes;
let spec;
let spec1;
let auction;
let ajaxStub;
let logMessageStub;
let logInfoStub;
let logWarnStub;
let logErrorStub;
let bids = TEST_BIDS;
let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })];
before(function () {
logMessageStub = sinon.stub(utils, 'logMessage');
logInfoStub = sinon.stub(utils, 'logInfo');
logWarnStub = sinon.stub(utils, 'logWarn');
logErrorStub = sinon.stub(utils, 'logError');
let bidRequests = [
mockBidRequest(bids[0]),
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 })
];
let makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests');
makeRequestsStub.returns(bidRequests);
ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder);
});
after(function () {
logMessageStub.restore();
logInfoStub.restore();
logWarnStub.restore();
logErrorStub.restore();
ajaxStub.restore();
adapterManager.makeBidRequests.restore();
});
beforeEach(function () {
config.setConfig({
debugging: {
enabled: true,
bidRequests: [{
bidderCode: BIDDER_CODE,
adUnitCode: ADUNIT_CODE,
storedAuctionResponse: '11111'
}]
}
});
adUnits = [{
code: ADUNIT_CODE,
bids: [
{bidder: BIDDER_CODE, params: {placementId: 'id'}},
]
}, {
code: ADUNIT_CODE1,
bids: [
{bidder: BIDDER_CODE1, params: {placementId: 'id'}},
]
}];
adUnitCodes = adUnits.map(({ code }) => code);
auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000});
createAuctionStub = sinon.stub(auctionModule, 'newAuction');
createAuctionStub.returns(auction);
spec = mockBidder(BIDDER_CODE, bids);
spec1 = mockBidder(BIDDER_CODE1, bids1);
registerBidder(spec);
registerBidder(spec1);
});
afterEach(function () {
logMessageStub.resetHistory();
logInfoStub.resetHistory();
logWarnStub.resetHistory();
logErrorStub.resetHistory();
auctionModule.newAuction.restore();
config.resetConfig();
});
it('should override bidRequest properties when config debugging has a matching bidRequest defined', function () {
auction.callBids();
const auctionBidRequests = auction.getBidRequests();
assert.equal(auctionBidRequests.length > 0, true);
assert.equal(Array.isArray(auctionBidRequests[0].bids), true);
const bid = find(auctionBidRequests[0].bids, bid => bid.adUnitCode === ADUNIT_CODE);
assert.equal(typeof bid !== 'undefined', true);
assert.equal(bid.hasOwnProperty('storedAuctionResponse'), true);
assert.equal(bid.storedAuctionResponse, '11111');
});
});
describe('getMediaTypeGranularity', function () {
it('video', function () {
let bidReq = {
'mediaTypes': { video: {id: '1'} }
};
// mediaType is video and video.context is undefined
expect(getMediaTypeGranularity('video', bidReq, {
banner: 'low',
video: 'medium'
})).to.equal('medium');
expect(getMediaTypeGranularity('video', {}, {
banner: 'low',
video: 'medium'
})).to.equal('medium');
``
expect(getMediaTypeGranularity('video', undefined, {
banner: 'low',
video: 'medium'
})).to.equal('medium');
// also when mediaTypes.video is undefined
bidReq = {
'mediaTypes': { banner: {} }
};
expect(getMediaTypeGranularity('video', bidReq, {
banner: 'low',
video: 'medium'
})).to.equal('medium');
// also when mediaTypes is undefined
expect(getMediaTypeGranularity('video', {}, {
banner: 'low',
video: 'medium'
})).to.equal('medium');
});
it('video-outstream', function () {
let bidReq = { 'mediaTypes': { video: { context: 'outstream' } } };
expect(getMediaTypeGranularity('video', bidReq, {
'banner': 'low', 'video': 'medium', 'video-outstream': 'high'
})).to.equal('high');
});
it('video-instream', function () {
let bidReq = { 'mediaTypes': { video: { context: 'instream' } } };
expect(getMediaTypeGranularity('video', bidReq, {
banner: 'low', video: 'medium', 'video-instream': 'high'
})).to.equal('high');
// fall back to video if video-instream not found
expect(getMediaTypeGranularity('video', bidReq, {
banner: 'low', video: 'medium'
})).to.equal('medium');
expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, {
banner: 'low', video: 'medium'
})).to.equal('medium');
});
it('native', function () {
expect(getMediaTypeGranularity('native', {mediaTypes: {native: {}}}, {
banner: 'low', video: 'medium', native: 'high'
})).to.equal('high');
});
});
describe('auctionCallbacks', function() {
let bids = TEST_BIDS;
let bidRequests;
let doneSpy;
let auction = {
getBidRequests: () => bidRequests,
getAuctionId: () => '1',
addBidReceived: () => true,
getTimeout: () => 1000
}
beforeEach(() => {
doneSpy = sinon.spy();
config.setConfig({
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
}
})
});
afterEach(() => {
doneSpy.resetHistory();
config.resetConfig();
});
it('should call auction done after bid is added to auction for mediaType banner', function () {
let ADUNIT_CODE2 = 'adUnitCode2';
let BIDDER_CODE2 = 'sampleBidder2';
let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })];
let bids2 = [mockBid({ bidderCode: BIDDER_CODE2 })];
bidRequests = [
mockBidRequest(bids[0]),
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE2 })
];
let cbs = auctionCallbacks(doneSpy, auction);
cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]);
cbs.adapterDone.call(bidRequests[0]);
cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]);
cbs.adapterDone.call(bidRequests[1]);
cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE2, bids2[0]);
cbs.adapterDone.call(bidRequests[2]);
assert.equal(doneSpy.callCount, 1);
});
it('should call auction done after prebid cache is complete for mediaType video', function() {
bids[0].mediaType = 'video';
let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })];
let opts = {
mediaType: {
video: {
context: 'instream',
playerSize: [640, 480],
},
}
};
bidRequests = [
mockBidRequest(bids[0], opts),
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }),
];
let cbs = auctionCallbacks(doneSpy, auction);
cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]);
cbs.adapterDone.call(bidRequests[0]);
cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]);
cbs.adapterDone.call(bidRequests[1]);
assert.equal(doneSpy.callCount, 0);
const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371';
const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`;
server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody);
assert.equal(doneSpy.callCount, 1);
})
});
describe('auctionOptions', function() {
let bidRequests;
let doneSpy;
let clock;
let auction = {
getBidRequests: () => bidRequests,
getAuctionId: () => '1',
addBidReceived: () => true,
getTimeout: () => 1000
}
let requiredBidder = BIDDER_CODE;
let requiredBidder1 = BIDDER_CODE1;
let secondaryBidder = 'doNotWaitForMe';
beforeEach(() => {
clock = sinon.useFakeTimers();
doneSpy = sinon.spy();
config.setConfig({
'auctionOptions': {
secondaryBidders: [ secondaryBidder ]
}
})
});
afterEach(() => {
doneSpy.resetHistory();
config.resetConfig();
clock.restore();
});
it('should not wait to call auction done for secondary bidders', function () {
let bids1 = [mockBid({ bidderCode: requiredBidder })];
let bids2 = [mockBid({ bidderCode: requiredBidder1 })];
let bids3 = [mockBid({ bidderCode: secondaryBidder })];
bidRequests = [
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }),
];
let cbs = auctionCallbacks(doneSpy, auction);
// required bidder responds immeaditely to auction
cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]);
cbs.adapterDone.call(bidRequests[0]);
assert.equal(doneSpy.callCount, 0);
// auction waits for second required bidder to respond
clock.tick(100);
cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]);
cbs.adapterDone.call(bidRequests[1]);
// auction done is reported and does not wait for secondaryBidder request
assert.equal(doneSpy.callCount, 1);
cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]);
cbs.adapterDone.call(bidRequests[2]);
});
it('should wait for all bidders if they are all secondary', function () {
config.setConfig({
'auctionOptions': {
secondaryBidders: [requiredBidder, requiredBidder1, secondaryBidder]
}
})
let bids1 = [mockBid({ bidderCode: requiredBidder })];
let bids2 = [mockBid({ bidderCode: requiredBidder1 })];
let bids3 = [mockBid({ bidderCode: secondaryBidder })];
bidRequests = [
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }),
];
let cbs = auctionCallbacks(doneSpy, auction);
cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]);
cbs.adapterDone.call(bidRequests[0]);
clock.tick(100);
assert.equal(doneSpy.callCount, 0)
cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]);
cbs.adapterDone.call(bidRequests[1]);
clock.tick(100);
assert.equal(doneSpy.callCount, 0);
cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]);
cbs.adapterDone.call(bidRequests[2]);
assert.equal(doneSpy.callCount, 1);
});
it('should allow secondaryBidders to respond in auction before is is done', function () {
let bids1 = [mockBid({ bidderCode: requiredBidder })];
let bids2 = [mockBid({ bidderCode: requiredBidder1 })];
let bids3 = [mockBid({ bidderCode: secondaryBidder })];
bidRequests = [
mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }),
mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }),
];
let cbs = auctionCallbacks(doneSpy, auction);
// secondaryBidder is first to respond
cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]);
cbs.adapterDone.call(bidRequests[2]);
clock.tick(100);
assert.equal(doneSpy.callCount, 0);
cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]);
cbs.adapterDone.call(bidRequests[1]);
clock.tick(100);
assert.equal(doneSpy.callCount, 0);
// first required bidder takes longest to respond, auction isn't marked as done until this occurs
cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]);
cbs.adapterDone.call(bidRequests[0]);
assert.equal(doneSpy.callCount, 1);
});
});
});