diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js
new file mode 100644
index 00000000000..00c78e2d443
--- /dev/null
+++ b/modules/prebidServerBidAdapter.js
@@ -0,0 +1,318 @@
+import Adapter from 'src/adapter';
+import bidfactory from 'src/bidfactory';
+import * as utils from 'src/utils';
+import { ajax } from 'src/ajax';
+import { STATUS, S2S } from 'src/constants';
+import { cookieSet } from 'src/cookie.js';
+import adaptermanager from 'src/adaptermanager';
+import { config } from 'src/config';
+import { VIDEO } from 'src/mediaTypes';
+
+const getConfig = config.getConfig;
+
+const TYPE = S2S.SRC;
+let _synced = false;
+
+let _s2sConfig;
+config.setDefaults({
+ 's2sConfig': {
+ enabled: false,
+ timeout: 1000,
+ maxBids: 1,
+ adapter: 'prebidServer'
+ }
+});
+
+/**
+ * Set config for server to server header bidding
+ * @typedef {Object} options - required
+ * @property {boolean} enabled enables S2S bidding
+ * @property {string[]} bidders bidders to request S2S
+ * @property {string} endpoint endpoint to contact
+ * === optional params below ===
+ * @property {number} [timeout] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})`
+ * @property {string} [adapter] adapter code to use for S2S
+ * @property {string} [syncEndpoint] endpoint URL for syncing cookies
+ * @property {string} [cookieSetUrl] url for cookie set library, if passed then cookieSet is enabled
+ */
+function setS2sConfig(options) {
+ let keys = Object.keys(options);
+
+ if (['accountId', 'bidders', 'endpoint'].filter(key => {
+ if (!keys.includes(key)) {
+ utils.logError(key + ' missing in server to server config');
+ return true;
+ }
+ return false;
+ }).length > 0) {
+ return;
+ }
+
+ _s2sConfig = options;
+ if (options.syncEndpoint) {
+ queueSync(options.bidders);
+ }
+}
+getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig));
+
+/**
+ * @param {Array} bidderCodes list of bidders to request user syncs for.
+ */
+function queueSync(bidderCodes) {
+ if (_synced) {
+ return;
+ }
+ _synced = true;
+ const payload = JSON.stringify({
+ uuid: utils.generateUUID(),
+ bidders: bidderCodes
+ });
+ ajax(_s2sConfig.syncEndpoint, (response) => {
+ try {
+ response = JSON.parse(response);
+ response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder));
+ } catch (e) {
+ utils.logError(e);
+ }
+ },
+ payload, {
+ contentType: 'text/plain',
+ withCredentials: true
+ });
+}
+
+/**
+ * Run a cookie sync for the given type, url, and bidder
+ *
+ * @param {string} type the type of sync, "image", "redirect", "iframe"
+ * @param {string} url the url to sync
+ * @param {string} bidder name of bidder doing sync for
+ */
+function doBidderSync(type, url, bidder) {
+ if (!url) {
+ utils.logError(`No sync url for bidder "${bidder}": ${url}`);
+ } else if (type === 'image' || type === 'redirect') {
+ utils.logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`);
+ utils.triggerPixel(url);
+ } else if (type == 'iframe') {
+ utils.logMessage(`Invoking iframe user sync for bidder: "${bidder}"`);
+ utils.insertUserSyncIframe(url);
+ } else {
+ utils.logError(`User sync type "${type}" not supported for bidder: "${bidder}"`);
+ }
+}
+
+/**
+ * Try to convert a value to a type.
+ * If it can't be done, the value will be returned.
+ *
+ * @param {string} typeToConvert The target type. e.g. "string", "number", etc.
+ * @param {*} value The value to be converted into typeToConvert.
+ */
+function tryConvertType(typeToConvert, value) {
+ if (typeToConvert === 'string') {
+ return value && value.toString();
+ } else if (typeToConvert === 'number') {
+ return Number(value);
+ } else {
+ return value;
+ }
+}
+
+const tryConvertString = tryConvertType.bind(null, 'string');
+const tryConvertNumber = tryConvertType.bind(null, 'number');
+
+const paramTypes = {
+ 'appnexus': {
+ 'member': tryConvertString,
+ 'invCode': tryConvertString,
+ 'placementId': tryConvertNumber
+ },
+ 'rubicon': {
+ 'accountId': tryConvertNumber,
+ 'siteId': tryConvertNumber,
+ 'zoneId': tryConvertNumber
+ },
+ 'indexExchange': {
+ 'siteID': tryConvertNumber
+ },
+ 'audienceNetwork': {
+ 'placementId': tryConvertString
+ },
+ 'pubmatic': {
+ 'publisherId': tryConvertString,
+ 'adSlot': tryConvertString
+ },
+ 'districtm': {
+ 'member': tryConvertString,
+ 'invCode': tryConvertString,
+ 'placementId': tryConvertNumber
+ },
+ 'pulsepoint': {
+ 'cf': tryConvertString,
+ 'cp': tryConvertNumber,
+ 'ct': tryConvertNumber
+ },
+};
+
+/**
+ * Bidder adapter for Prebid Server
+ */
+export function PrebidServer() {
+ let baseAdapter = new Adapter('prebidServer');
+
+ function convertTypes(adUnits) {
+ adUnits.forEach(adUnit => {
+ adUnit.bids.forEach(bid => {
+ const types = paramTypes[bid.bidder] || [];
+ Object.keys(types).forEach(key => {
+ if (bid.params[key]) {
+ bid.params[key] = types[key](bid.params[key]);
+
+ // don't send invalid values
+ if (isNaN(bid.params[key])) {
+ delete bid.params.key;
+ }
+ }
+ });
+ });
+ });
+ }
+
+ /* Prebid executes this function when the page asks to send out bid requests */
+ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) {
+ const isDebug = !!getConfig('debug');
+ const adUnits = utils.cloneJson(s2sBidRequest.ad_units);
+ adUnits.forEach(adUnit => {
+ let videoMediaType = utils.deepAccess(adUnit, 'mediaTypes.video');
+ if (videoMediaType) {
+ // pbs expects a ad_unit.video attribute if the imp is video
+ adUnit.video = Object.assign({}, videoMediaType);
+ delete adUnit.mediaTypes;
+ // default is assumed to be 'banner' so if there is a video type we assume video only until PBS can support multi format auction.
+ adUnit.media_types = [VIDEO];
+ }
+ });
+ convertTypes(adUnits);
+ let requestJson = {
+ account_id: _s2sConfig.accountId,
+ tid: s2sBidRequest.tid,
+ max_bids: _s2sConfig.maxBids,
+ timeout_millis: _s2sConfig.timeout,
+ secure: _s2sConfig.secure,
+ url: utils.getTopWindowUrl(),
+ prebid_version: '$prebid.version$',
+ ad_units: adUnits.filter(hasSizes),
+ is_debug: isDebug
+ };
+
+ // in case config.bidders contains invalid bidders, we only process those we sent requests for.
+ const requestedBidders = requestJson.ad_units.map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)).reduce(utils.flatten).filter(utils.uniques);
+ function processResponse(response) {
+ handleResponse(response, requestedBidders, bidRequests, addBidResponse, done);
+ }
+ const payload = JSON.stringify(requestJson);
+ ajax(_s2sConfig.endpoint, processResponse, payload, {
+ contentType: 'text/plain',
+ withCredentials: true
+ });
+ };
+
+ // at this point ad units should have a size array either directly or mapped so filter for that
+ function hasSizes(unit) {
+ return unit.sizes && unit.sizes.length;
+ }
+
+ /* Notify Prebid of bid responses so bids can get in the auction */
+ function handleResponse(response, requestedBidders, bidRequests, addBidResponse, done) {
+ let result;
+ try {
+ result = JSON.parse(response);
+
+ if (result.status === 'OK' || result.status === 'no_cookie') {
+ if (result.bidder_status) {
+ result.bidder_status.forEach(bidder => {
+ if (bidder.no_cookie) {
+ doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder);
+ }
+ });
+ }
+
+ // do client-side syncs if available
+ requestedBidders.forEach(bidder => {
+ let clientAdapter = adaptermanager.getBidAdapter(bidder);
+ if (clientAdapter && clientAdapter.registerSyncs) {
+ clientAdapter.registerSyncs();
+ }
+ });
+
+ if (result.bids) {
+ result.bids.forEach(bidObj => {
+ let bidRequest = utils.getBidRequest(bidObj.bid_id, bidRequests);
+ let cpm = bidObj.price;
+ let status;
+ if (cpm !== 0) {
+ status = STATUS.GOOD;
+ } else {
+ status = STATUS.NO_BID;
+ }
+
+ let bidObject = bidfactory.createBid(status, bidRequest);
+ bidObject.source = TYPE;
+ bidObject.creative_id = bidObj.creative_id;
+ bidObject.bidderCode = bidObj.bidder;
+ bidObject.cpm = cpm;
+ // From ORTB see section 4.2.3: adm Optional means of conveying ad markup in case the bid wins; supersedes the win notice if markup is included in both.
+ if (bidObj.media_type === VIDEO) {
+ bidObject.mediaType = VIDEO;
+ if (bidObj.adm) {
+ bidObject.vastXml = bidObj.adm;
+ }
+ if (bidObj.nurl) {
+ bidObject.vastUrl = bidObj.nurl;
+ }
+ } else {
+ if (bidObj.adm && bidObj.nurl) {
+ bidObject.ad = bidObj.adm;
+ bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bidObj.nurl));
+ } else if (bidObj.adm) {
+ bidObject.ad = bidObj.adm;
+ } else if (bidObj.nurl) {
+ bidObject.adUrl = bidObj.nurl
+ }
+ }
+
+ bidObject.width = bidObj.width;
+ bidObject.height = bidObj.height;
+ bidObject.adserverTargeting = bidObj.ad_server_targeting;
+ if (bidObj.deal_id) {
+ bidObject.dealId = bidObj.deal_id;
+ }
+
+ addBidResponse(bidObj.code, bidObject);
+ });
+ }
+ }
+ if (result.status === 'no_cookie' && typeof _s2sConfig.cookieSetUrl === 'string') {
+ // cookie sync
+ cookieSet(_s2sConfig.cookieSetUrl);
+ }
+ } catch (error) {
+ utils.logError(error);
+ }
+
+ if (!result || (result.status && result.status.includes('Error'))) {
+ utils.logError('error parsing response: ', result.status);
+ }
+
+ done();
+ }
+
+ return Object.assign(this, {
+ callBids: baseAdapter.callBids,
+ setBidderCode: baseAdapter.setBidderCode,
+ type: TYPE
+ });
+}
+
+adaptermanager.registerBidAdapter(new PrebidServer(), 'prebidServer');
diff --git a/src/adaptermanager.js b/src/adaptermanager.js
index c6568c0c9f7..a009e91cd24 100644
--- a/src/adaptermanager.js
+++ b/src/adaptermanager.js
@@ -15,7 +15,10 @@ let s2sTestingModule; // store s2sTesting module if it's loaded
var _bidderRegistry = {};
exports.bidderRegistry = _bidderRegistry;
-let _s2sConfig = config.getConfig('s2sConfig');
+let _s2sConfig = {};
+config.getConfig('s2sConfig', config => {
+ _s2sConfig = config.s2sConfig;
+});
var _analyticsRegistry = {};
@@ -89,19 +92,30 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}) {
}, []).reduce(flatten, []).filter(val => val !== '');
}
+function transformHeightWidth(adUnit) {
+ let sizesObj = [];
+ let sizes = utils.parseSizesInput(adUnit.sizes);
+ sizes.forEach(size => {
+ let heightWidth = size.split('x');
+ let sizeObj = {
+ 'w': parseInt(heightWidth[0]),
+ 'h': parseInt(heightWidth[1])
+ };
+ sizesObj.push(sizeObj);
+ });
+ return sizesObj;
+}
+
function getAdUnitCopyForPrebidServer(adUnits) {
let adaptersServerSide = _s2sConfig.bidders;
let adUnitsCopy = utils.cloneJson(adUnits);
- // filter out client side bids
adUnitsCopy.forEach((adUnit) => {
- if (adUnit.sizeMapping) {
- adUnit.sizes = mapSizes(adUnit);
- delete adUnit.sizeMapping;
- }
adUnit.sizes = transformHeightWidth(adUnit);
+
+ // filter out client side bids
adUnit.bids = adUnit.bids.filter((bid) => {
- return adaptersServerSide.includes(bid.bidder) && (!s2sTesting || bid.finalSource !== s2sTestingModule.CLIENT);
+ return adaptersServerSide.includes(bid.bidder) && (!doingS2STesting() || bid.finalSource !== s2sTestingModule.CLIENT);
}).map((bid) => {
bid.bid_id = utils.getUniqueIdentifierStr();
return bid;
@@ -115,6 +129,23 @@ function getAdUnitCopyForPrebidServer(adUnits) {
return adUnitsCopy;
}
+function getAdUnitCopyForClientAdapters(adUnits) {
+ let adUnitsClientCopy = utils.cloneJson(adUnits);
+ // filter out s2s bids
+ adUnitsClientCopy.forEach((adUnit) => {
+ adUnit.bids = adUnit.bids.filter((bid) => {
+ return !doingS2STesting() || bid.finalSource !== s2sTestingModule.SERVER;
+ })
+ });
+
+ // don't send empty requests
+ adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => {
+ return adUnit.bids.length !== 0;
+ });
+
+ return adUnitsClientCopy;
+}
+
exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) {
let bidRequests = [];
let bidderCodes = getBidderCodes(adUnits);
@@ -122,18 +153,11 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
bidderCodes = shuffle(bidderCodes);
}
- const s2sAdapter = _bidderRegistry[_s2sConfig.adapter];
- if (s2sAdapter) {
- s2sAdapter.setConfig(_s2sConfig);
- s2sAdapter.queueSync({bidderCodes});
- }
-
+ let clientBidderCodes = bidderCodes;
let clientTestAdapters = [];
- let s2sTesting = false;
if (_s2sConfig.enabled) {
// if s2sConfig.bidderControl testing is turned on
- s2sTesting = _s2sConfig.testing && typeof s2sTestingModule !== 'undefined';
- if (s2sTesting) {
+ if (doingS2STesting()) {
// get all adapters doing client testing
clientTestAdapters = s2sTestingModule.getSourceBidderMap(adUnits)[s2sTestingModule.CLIENT];
}
@@ -142,11 +166,11 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
let adaptersServerSide = _s2sConfig.bidders;
// don't call these client side (unless client request is needed for testing)
- bidderCodes = bidderCodes.filter((elm) => {
+ clientBidderCodes = bidderCodes.filter((elm) => {
return !adaptersServerSide.includes(elm) || clientTestAdapters.includes(elm);
});
- let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits);
+ let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits);
let tid = utils.generateUUID();
adaptersServerSide.forEach(bidderCode => {
const bidderRequestId = utils.getUniqueIdentifierStr();
@@ -166,27 +190,15 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
});
}
- // client side adapters
- let adUnitsClientCopy = utils.cloneJson(adUnits);
- // filter out s2s bids
- adUnitsClientCopy.forEach((adUnit) => {
- adUnit.bids = adUnit.bids.filter((bid) => {
- return !s2sTesting || bid.finalSource !== s2sTestingModule.SERVER;
- })
- });
-
- // don't send empty requests
- adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => {
- return adUnit.bids.length !== 0;
- });
-
- bidderCodes.forEach(bidderCode => {
+ // client adapters
+ let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits);
+ clientBidderCodes.forEach(bidderCode => {
const bidderRequestId = utils.getUniqueIdentifierStr();
const bidderRequest = {
bidderCode,
auctionId,
bidderRequestId,
- bids: getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}),
+ bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsClientCopy, labels}),
auctionStart: auctionStart,
timeout: cbTimeout
};
@@ -198,9 +210,17 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
};
exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => {
- let serverBidRequests = bidRequests.filter(bidRequest => {
- return bidRequest.src && bidRequest.src === CONSTANTS.S2S.SRC;
- });
+ if (!bidRequests.length) {
+ utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?');
+ return;
+ }
+
+ let ajax = ajaxBuilder(bidRequests[0].timeout);
+
+ let [clientBidRequests, serverBidRequests] = bidRequests.reduce((partitions, bidRequest) => {
+ partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC)].push(bidRequest);
+ return partitions;
+ }, [[], []]);
if (serverBidRequests.length) {
let adaptersServerSide = _s2sConfig.bidders;
@@ -209,45 +229,56 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => {
if (s2sAdapter) {
let s2sBidRequest = {tid, 'ad_units': getAdUnitCopyForPrebidServer(adUnits)};
- utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.join(',')}`);
if (s2sBidRequest.ad_units.length) {
- s2sAdapter.callBids(s2sBidRequest);
+ let doneCbs = serverBidRequests.map(bidRequest => {
+ bidRequest.doneCbCallCount = 0;
+ return doneCb(bidRequest.bidderRequestId)
+ });
+
+ // only log adapters that actually have adUnit bids
+ let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => {
+ return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => { return adapters.concat(bid.bidderCode) }, []));
+ }, []);
+ utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => {
+ return allBidders.includes(adapter);
+ }).join(',')}`);
+
+ // fire BID_REQUESTED event for each s2s bidRequest
+ serverBidRequests.forEach(bidRequest => {
+ events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
+ });
+
+ // make bid requests
+ s2sAdapter.callBids(
+ s2sBidRequest,
+ serverBidRequests,
+ addBidResponse,
+ () => doneCbs.forEach(done => done()),
+ ajax
+ );
}
}
}
- if (bidRequests.length) {
- let ajax = ajaxBuilder(bidRequests[0].timeout);
- bidRequests.forEach(bidRequest => {
- bidRequest.start = new Date().getTime();
- // TODO : Do we check for bid in pool from here and skip calling adapter again ?
- const adapter = _bidderRegistry[bidRequest.bidderCode];
- if (adapter) {
- utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
- events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
- bidRequest.doneCbCallCount = 0;
- let done = doneCb(bidRequest.bidderRequestId);
- adapter.callBids(bidRequest, addBidResponse, done, ajax);
- } else {
- utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`);
- }
- });
- } else {
- utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?');
- }
-};
-function transformHeightWidth(adUnit) {
- let sizesObj = [];
- let sizes = utils.parseSizesInput(adUnit.sizes);
- sizes.forEach(size => {
- let heightWidth = size.split('x');
- let sizeObj = {
- 'w': parseInt(heightWidth[0]),
- 'h': parseInt(heightWidth[1])
- };
- sizesObj.push(sizeObj);
+ // handle client adapter requests
+ clientBidRequests.forEach(bidRequest => {
+ bidRequest.start = new Date().getTime();
+ // TODO : Do we check for bid in pool from here and skip calling adapter again ?
+ const adapter = _bidderRegistry[bidRequest.bidderCode];
+ if (adapter) {
+ utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
+ events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
+ bidRequest.doneCbCallCount = 0;
+ let done = doneCb(bidRequest.bidderRequestId);
+ adapter.callBids(bidRequest, addBidResponse, done, ajax);
+ } else {
+ utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`);
+ }
});
- return sizesObj;
+}
+
+function doingS2STesting() {
+ return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule;
}
function getSupportedMediaTypes(bidderCode) {
@@ -340,6 +371,10 @@ exports.enableAnalytics = function (config) {
});
};
+exports.getBidAdapter = function(bidder) {
+ return _bidderRegistry[bidder];
+};
+
// the s2sTesting module is injected when it's loaded rather than being imported
// importing it causes the packager to include it even when it's not explicitly included in the build
exports.setS2STestingModule = function (module) {
diff --git a/src/config.js b/src/config.js
index bbde171576c..9ff57777984 100644
--- a/src/config.js
+++ b/src/config.js
@@ -22,16 +22,6 @@ const DEFAULT_USERSYNC = {
syncDelay: 3000
};
const DEFAULT_TIMEOUTBUFFER = 200;
-const DEFAULT_S2SCONFIG = {
- enabled: false,
- endpoint: 'https://prebid.adnxs.com/pbs/v1/auction',
- timeout: 1000,
- maxBids: 1,
- adapter: 'prebidServer',
- syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync',
- cookieSet: true,
- bidders: []
-};
export const RANDOM = 'random';
const FIXED = 'fixed';
@@ -64,6 +54,8 @@ const ALL_TOPICS = '*';
export function newConfig() {
let listeners = [];
+ let defaults = {};
+
let config = {
// `debug` is equivalent to legacy `pbjs.logging` property
_debug: DEFAULT_DEBUG,
@@ -152,24 +144,6 @@ export function newConfig() {
this._timoutBuffer = val;
},
- _s2sConfig: DEFAULT_S2SCONFIG,
- get s2sConfig() {
- return this._s2sConfig;
- },
- set s2sConfig(val) {
- if (!utils.contains(Object.keys(val), 'accountId')) {
- utils.logError('accountId missing in Server to Server config');
- return;
- }
-
- if (!utils.contains(Object.keys(val), 'bidders')) {
- utils.logError('bidders missing in Server to Server config');
- return;
- }
-
- this._s2sConfig = Object.assign({}, DEFAULT_S2SCONFIG, val);
- },
-
// userSync defaults
userSync: DEFAULT_USERSYNC
};
@@ -223,8 +197,33 @@ export function newConfig() {
return;
}
- Object.assign(config, options);
- callSubscribers(options);
+ let topics = Object.keys(options);
+ let topicalConfig = {};
+
+ topics.forEach(topic => {
+ let option = options[topic];
+
+ if (typeof defaults[topic] === 'object' && typeof option === 'object') {
+ option = Object.assign({}, defaults[topic], option);
+ }
+
+ topicalConfig[topic] = config[topic] = option;
+ });
+
+ callSubscribers(topicalConfig);
+ }
+
+ /**
+ * Sets configuration defaults which setConfig values can be applied on top of
+ * @param {object} options
+ */
+ function setDefaults(options) {
+ if (typeof defaults !== 'object') {
+ utils.logError('defaults must be an object');
+ return;
+ }
+
+ Object.assign(defaults, options);
}
/*
@@ -292,7 +291,8 @@ export function newConfig() {
return {
getConfig,
- setConfig
+ setConfig,
+ setDefaults
};
}
diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js
new file mode 100644
index 00000000000..608ac102ace
--- /dev/null
+++ b/test/spec/modules/prebidServerBidAdapter_spec.js
@@ -0,0 +1,479 @@
+import { expect } from 'chai';
+import { PrebidServer as Adapter } from 'modules/prebidServerBidAdapter';
+import adapterManager from 'src/adaptermanager';
+import * as utils from 'src/utils';
+import cookie from 'src/cookie';
+import { userSync } from 'src/userSync';
+import { ajax } from 'src/ajax';
+import { config } from 'src/config';
+
+let CONFIG = {
+ accountId: '1',
+ enabled: true,
+ bidders: ['appnexus'],
+ timeout: 1000,
+ endpoint: 'https://prebid.adnxs.com/pbs/v1/auction'
+};
+
+const REQUEST = {
+ 'account_id': '1',
+ 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
+ 'max_bids': 1,
+ 'timeout_millis': 1000,
+ 'secure': 0,
+ 'url': '',
+ 'prebid_version': '0.30.0-pre',
+ 'ad_units': [
+ {
+ 'code': 'div-gpt-ad-1460505748561-0',
+ 'sizes': [
+ {
+ 'w': 300,
+ 'h': 250
+ },
+ {
+ 'w': 300,
+ 'h': 600
+ }
+ ],
+ 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c',
+ 'bids': [
+ {
+ 'bid_id': '123',
+ 'bidder': 'appnexus',
+ 'params': {
+ 'placementId': '10433394',
+ 'member': 123
+ }
+ }
+ ]
+ }
+ ]
+};
+
+const BID_REQUESTS = [
+ {
+ 'bidderCode': 'appnexus',
+ 'auctionId': '173afb6d132ba3',
+ 'bidderRequestId': '3d1063078dfcc8',
+ 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
+ 'bids': [
+ {
+ 'bidder': 'appnexus',
+ 'params': {
+ 'placementId': '10433394',
+ 'member': 123
+ },
+ 'bid_id': '123',
+ 'adUnitCode': 'div-gpt-ad-1460505748561-0',
+ 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c',
+ 'sizes': [
+ {
+ 'w': 300,
+ 'h': 250
+ }
+ ],
+ 'bidId': '259fb43aaa06c1',
+ 'bidderRequestId': '3d1063078dfcc8',
+ 'auctionId': '173afb6d132ba3'
+ }
+ ],
+ 'auctionStart': 1510852447530,
+ 'timeout': 5000,
+ 'src': 's2s',
+ 'doneCbCallCount': 0
+ }
+];
+
+const RESPONSE = {
+ 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
+ 'status': 'OK',
+ 'bidder_status': [
+ {
+ 'bidder': 'appnexus',
+ 'response_time_ms': 52,
+ 'num_bids': 1
+ }
+ ],
+ 'bids': [
+ {
+ 'bid_id': '123',
+ 'code': 'div-gpt-ad-1460505748561-0',
+ 'creative_id': '29681110',
+ 'bidder': 'appnexus',
+ 'price': 0.5,
+ 'adm': '',
+ 'width': 300,
+ 'height': 250,
+ 'deal_id': 'test-dealid',
+ 'ad_server_targeting': {
+ 'foo': 'bar'
+ }
+ }
+ ]
+};
+
+const RESPONSE_NO_BID_NO_UNIT = {
+ 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
+ 'status': 'OK',
+ 'bidder_status': [{
+ 'bidder': 'appnexus',
+ 'response_time_ms': 132,
+ 'no_bid': true
+ }]
+};
+
+const RESPONSE_NO_BID_UNIT_SET = {
+ 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
+ 'status': 'OK',
+ 'bidder_status': [{
+ 'bidder': 'appnexus',
+ 'ad_unit': 'div-gpt-ad-1460505748561-0',
+ 'response_time_ms': 91,
+ 'no_bid': true
+ }]
+};
+
+const RESPONSE_NO_COOKIE = {
+ 'tid': 'd6eca075-4a59-4346-bdb3-86531830ef2c',
+ 'status': 'OK',
+ 'bidder_status': [{
+ 'bidder': 'pubmatic',
+ 'no_cookie': true,
+ 'usersync': {
+ 'url': '//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=http://localhost:8000/setuid?bidder=pubmatic&uid=',
+ 'type': 'iframe'
+ }
+ }]
+};
+
+const RESPONSE_NO_PBS_COOKIE = {
+ 'tid': '882fe33e-2981-4257-bd44-bd3b03945f48',
+ 'status': 'no_cookie',
+ 'bidder_status': [{
+ 'bidder': 'rubicon',
+ 'no_cookie': true,
+ 'usersync': {
+ 'url': 'https://pixel.rubiconproject.com/exchange/sync.php?p=prebid',
+ 'type': 'redirect'
+ }
+ }, {
+ 'bidder': 'pubmatic',
+ 'no_cookie': true,
+ 'usersync': {
+ 'url': '//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dpubmatic%26uid%3D',
+ 'type': 'iframe'
+ }
+ }, {
+ 'bidder': 'appnexus',
+ 'response_time_ms': 162,
+ 'num_bids': 1,
+ 'debug': [{
+ 'request_uri': 'http://ib.adnxs.com/openrtb2',
+ 'request_body': '{"id":"882fe33e-2981-4257-bd44-bd3b03945f48","imp":[{"id":"/19968336/header-bid-tag-0","banner":{"w":300,"h":250,"format":[{"w":300,"h":250}]},"secure":1,"ext":{"appnexus":{"placement_id":5914989}}}],"site":{"domain":"nytimes.com","page":"http://www.nytimes.com"},"device":{"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36","ip":"75.97.0.47"},"user":{"id":"3519479852893340159","buyeruid":"3519479852893340159"},"at":1,"tmax":1000,"source":{"fd":1,"tid":"882fe33e-2981-4257-bd44-bd3b03945f48"}}',
+ 'response_body': '{"id":"882fe33e-2981-4257-bd44-bd3b03945f48"}',
+ 'status_code': 200
+ }]
+ }],
+ 'bids': [{
+ 'bid_id': '123',
+ 'code': 'div-gpt-ad-1460505748561-0',
+ 'creative_id': '70928274',
+ 'bidder': 'appnexus',
+ 'price': 0.07425,
+ 'adm': '',
+ 'width': 300,
+ 'height': 250,
+ 'response_time_ms': 162
+ }]
+};
+
+const RESPONSE_NO_PBS_COOKIE_ERROR = {
+ 'tid': '882fe33e-2981-4257-bd44-bd3b0394545f',
+ 'status': 'no_cookie',
+ 'bidder_status': [{
+ 'bidder': 'rubicon',
+ 'no_cookie': true,
+ 'usersync': {
+ 'url': 'https://pixel.rubiconproject.com/exchange/sync.php?p=prebid',
+ 'type': 'jsonp'
+ }
+ }, {
+ 'bidder': 'pubmatic',
+ 'no_cookie': true,
+ 'usersync': {
+ 'url': '',
+ 'type': 'iframe'
+ }
+ }]
+};
+
+describe('S2S Adapter', () => {
+ let adapter,
+ addBidResponse = sinon.spy(),
+ done = sinon.spy();
+
+ beforeEach(() => adapter = new Adapter());
+
+ afterEach(() => {
+ addBidResponse.reset();
+ done.reset();
+ });
+
+ describe('request function', () => {
+ let xhr;
+ let requests;
+
+ beforeEach(() => {
+ xhr = sinon.useFakeXMLHttpRequest();
+ requests = [];
+ xhr.onCreate = request => requests.push(request);
+ });
+
+ afterEach(() => xhr.restore());
+
+ it('exists and is a function', () => {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+
+ it('exists converts types', () => {
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ const requestBid = JSON.parse(requests[0].requestBody);
+ expect(requestBid.ad_units[0].bids[0].params.placementId).to.exist.and.to.be.a('number');
+ expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string');
+ });
+ });
+
+ describe('response handler', () => {
+ let server;
+
+ beforeEach(() => {
+ server = sinon.fakeServer.create();
+ sinon.stub(utils, 'triggerPixel');
+ sinon.stub(utils, 'insertUserSyncIframe');
+ sinon.stub(utils, 'logError');
+ sinon.stub(cookie, 'cookieSet');
+ sinon.stub(utils, 'getBidRequest').returns({
+ bidId: '123'
+ });
+ });
+
+ afterEach(() => {
+ server.restore();
+ utils.getBidRequest.restore();
+ utils.triggerPixel.restore();
+ utils.insertUserSyncIframe.restore();
+ utils.logError.restore();
+ cookie.cookieSet.restore();
+ });
+
+ // TODO: test dependent on pbjs_api_spec. Needs to be isolated
+ it('registers bids', () => {
+ server.respondWith(JSON.stringify(RESPONSE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ sinon.assert.calledOnce(addBidResponse);
+
+ const response = addBidResponse.firstCall.args[1];
+ expect(response).to.have.property('statusMessage', 'Bid available');
+ expect(response).to.have.property('cpm', 0.5);
+ expect(response).to.have.property('adId', '123');
+ });
+
+ it('does not call addBidResponse and calls done when ad unit not set', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.notCalled(addBidResponse);
+ sinon.assert.calledOnce(done);
+ });
+
+ it('does not call addBidResponse and calls done when server requests cookie sync', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_COOKIE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.notCalled(addBidResponse);
+ sinon.assert.calledOnce(done);
+ });
+
+ it('does not call addBidResponse and calls done when ad unit is set', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_BID_UNIT_SET));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.notCalled(addBidResponse);
+ sinon.assert.calledOnce(done);
+ });
+
+ it('registers successful bids and calls done when there are less bids than requests', () => {
+ server.respondWith(JSON.stringify(RESPONSE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.calledOnce(addBidResponse);
+ sinon.assert.calledOnce(done);
+
+ expect(addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0');
+
+ expect(addBidResponse.firstCall.args[1]).to.have.property('adId', '123');
+
+ expect(addBidResponse.firstCall.args[1])
+ .to.have.property('statusMessage', 'Bid available');
+ });
+
+ it('should have dealId in bidObject', () => {
+ server.respondWith(JSON.stringify(RESPONSE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ const response = addBidResponse.firstCall.args[1];
+ expect(response).to.have.property('dealId', 'test-dealid');
+ });
+
+ it('should pass through default adserverTargeting if present in bidObject', () => {
+ server.respondWith(JSON.stringify(RESPONSE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ const response = addBidResponse.firstCall.args[1];
+ expect(response).to.have.property('adserverTargeting').that.deep.equals({'foo': 'bar'});
+ });
+
+ it('registers client user syncs when client bid adapter is present', () => {
+ let rubiconAdapter = {
+ registerSyncs: sinon.spy()
+ };
+ sinon.stub(adapterManager, 'getBidAdapter', () => rubiconAdapter);
+
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.calledOnce(rubiconAdapter.registerSyncs);
+
+ adapterManager.getBidAdapter.restore();
+ });
+
+ it('registers bid responses when server requests cookie sync', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ sinon.assert.calledOnce(addBidResponse);
+
+ const ad_unit_code = addBidResponse.firstCall.args[0];
+ expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0');
+
+ const response = addBidResponse.firstCall.args[1];
+ expect(response).to.have.property('statusMessage', 'Bid available');
+ expect(response).to.have.property('source', 's2s');
+
+ const bid_request_passed = addBidResponse.firstCall.args[1];
+ expect(bid_request_passed).to.have.property('adId', '123');
+ });
+
+ it('does cookie sync when no_cookie response', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.calledOnce(utils.triggerPixel);
+ sinon.assert.calledWith(utils.triggerPixel, 'https://pixel.rubiconproject.com/exchange/sync.php?p=prebid');
+ sinon.assert.calledOnce(utils.insertUserSyncIframe);
+ sinon.assert.calledWith(utils.insertUserSyncIframe, '//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dpubmatic%26uid%3D');
+ });
+
+ it('logs error when no_cookie response is missing type or url', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE_ERROR));
+
+ config.setConfig({s2sConfig: CONFIG});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.notCalled(utils.triggerPixel);
+ sinon.assert.notCalled(utils.insertUserSyncIframe);
+ sinon.assert.calledTwice(utils.logError);
+ });
+
+ it('does not call cookieSet cookie sync when no_cookie response && not opted in', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
+
+ let myConfig = Object.assign({}, CONFIG);
+
+ config.setConfig({s2sConfig: myConfig});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ sinon.assert.notCalled(cookie.cookieSet);
+ });
+
+ it('calls cookieSet cookie sync when no_cookie response && opted in', () => {
+ server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
+ let myConfig = Object.assign({
+ cookieSetUrl: 'https://acdn.adnxs.com/cookieset/cs.js'
+ }, CONFIG);
+
+ config.setConfig({s2sConfig: myConfig});
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+ sinon.assert.calledOnce(cookie.cookieSet);
+ });
+ });
+
+ describe('s2sConfig', () => {
+ let logErrorSpy;
+
+ beforeEach(() => {
+ logErrorSpy = sinon.spy(utils, 'logError');
+ });
+
+ afterEach(() => {
+ utils.logError.restore();
+ });
+
+ it('should log error when accountId is missing', () => {
+ const options = {
+ enabled: true,
+ bidders: ['appnexus'],
+ timeout: 1000,
+ adapter: 'prebidServer',
+ endpoint: 'https://prebid.adnxs.com/pbs/v1/auction'
+ };
+
+ config.setConfig({ s2sConfig: options });
+ sinon.assert.calledOnce(logErrorSpy);
+ });
+
+ it('should log error when bidders is missing', () => {
+ const options = {
+ accountId: '1',
+ enabled: true,
+ timeout: 1000,
+ adapter: 's2s',
+ endpoint: 'https://prebid.adnxs.com/pbs/v1/auction'
+ };
+
+ config.setConfig({ s2sConfig: options });
+ sinon.assert.calledOnce(logErrorSpy);
+ });
+ });
+});
diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js
index ae09d8598bb..5f21c5355d8 100644
--- a/test/spec/unit/core/adapterManager_spec.js
+++ b/test/spec/unit/core/adapterManager_spec.js
@@ -7,6 +7,7 @@ import { config } from 'src/config';
import { registerBidder } from 'src/adapters/bidderFactory';
import { setSizeConfig } from 'src/sizeMapping';
var s2sTesting = require('../../../../modules/s2sTesting');
+var events = require('../../../../src/events');
const CONFIG = {
enabled: true,
@@ -18,21 +19,15 @@ const CONFIG = {
};
var prebidServerAdapterMock = {
bidder: 'prebidServer',
- callBids: sinon.stub(),
- setConfig: sinon.stub(),
- queueSync: sinon.stub()
+ callBids: sinon.stub()
};
var adequantAdapterMock = {
bidder: 'adequant',
- callBids: sinon.stub(),
- setConfig: sinon.stub(),
- queueSync: sinon.stub()
+ callBids: sinon.stub()
};
var appnexusAdapterMock = {
bidder: 'appnexus',
- callBids: sinon.stub(),
- setConfig: sinon.stub(),
- queueSync: sinon.stub()
+ callBids: sinon.stub()
};
describe('adapterManager tests', () => {
@@ -96,6 +91,22 @@ describe('adapterManager tests', () => {
sinon.assert.called(utils.logError);
});
+
+ it('should emit BID_REQUESTED event', () => {
+ // function to count BID_REQUESTED events
+ let cnt = 0;
+ let count = () => cnt++;
+ events.on(CONSTANTS.EVENTS.BID_REQUESTED, count);
+ AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock;
+ let adUnits = getAdUnits();
+ let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000);
+ AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {});
+ expect(cnt).to.equal(1);
+ sinon.assert.calledOnce(appnexusAdapterMock.callBids);
+ appnexusAdapterMock.callBids.reset();
+ delete AdapterManager.bidderRegistry['appnexus'];
+ events.off(CONSTANTS.EVENTS.BID_REQUESTED, count);
+ });
});
describe('S2S tests', () => {
@@ -106,8 +117,7 @@ describe('adapterManager tests', () => {
prebidServerAdapterMock.callBids.reset();
});
- // Enable this test when prebidServer adapter is made 1.0 compliant
- it.skip('invokes callBids on the S2S adapter', () => {
+ it('invokes callBids on the S2S adapter', () => {
let bidRequests = [{
'bidderCode': 'appnexus',
'requestId': '1863e370099523',
@@ -166,12 +176,17 @@ describe('adapterManager tests', () => {
'start': 1462918897460
}];
- AdapterManager.callBids(getAdUnits(), bidRequests);
+ AdapterManager.callBids(
+ getAdUnits(),
+ bidRequests,
+ () => {},
+ () => () => {}
+ );
sinon.assert.calledOnce(prebidServerAdapterMock.callBids);
});
// Enable this test when prebidServer adapter is made 1.0 compliant
- it.skip('invokes callBids with only s2s bids', () => {
+ it('invokes callBids with only s2s bids', () => {
const adUnits = getAdUnits();
// adUnit without appnexus bidder
adUnits.push({
@@ -245,13 +260,237 @@ describe('adapterManager tests', () => {
],
'start': 1462918897460
}];
- AdapterManager.callBids(adUnits, bidRequests);
+ AdapterManager.callBids(
+ adUnits,
+ bidRequests,
+ () => {},
+ () => () => {}
+ );
const requestObj = prebidServerAdapterMock.callBids.firstCall.args[0];
expect(requestObj.ad_units.length).to.equal(2);
sinon.assert.calledOnce(prebidServerAdapterMock.callBids);
});
+
+ describe('BID_REQUESTED event', () => {
+ // function to count BID_REQUESTED events
+ let cnt, count = () => cnt++;
+
+ beforeEach(() => {
+ cnt = 0;
+ events.on(CONSTANTS.EVENTS.BID_REQUESTED, count);
+ });
+
+ afterEach(() => {
+ events.off(CONSTANTS.EVENTS.BID_REQUESTED, count);
+ });
+
+ it('should fire for s2s requests', () => {
+ let adUnits = getAdUnits();
+ let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000);
+ AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {});
+ expect(cnt).to.equal(1);
+ sinon.assert.calledOnce(prebidServerAdapterMock.callBids);
+ });
+
+ it('should fire for simultaneous s2s and client requests', () => {
+ AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock;
+ let adUnits = getAdUnits();
+ let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000);
+ AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {});
+ expect(cnt).to.equal(2);
+ sinon.assert.calledOnce(prebidServerAdapterMock.callBids);
+ sinon.assert.calledOnce(adequantAdapterMock.callBids);
+ adequantAdapterMock.callBids.reset();
+ delete AdapterManager.bidderRegistry['adequant'];
+ });
+ });
}); // end s2s tests
+ describe('s2sTesting', () => {
+ function getTestAdUnits() {
+ // copy adUnits
+ return JSON.parse(JSON.stringify(getAdUnits()));
+ }
+
+ function callBids(adUnits = getTestAdUnits()) {
+ let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000);
+ AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {});
+ }
+
+ function checkServerCalled(numAdUnits, numBids) {
+ sinon.assert.calledOnce(prebidServerAdapterMock.callBids);
+ let requestObj = prebidServerAdapterMock.callBids.firstCall.args[0];
+ expect(requestObj.ad_units.length).to.equal(numAdUnits);
+ for (let i = 0; i < numAdUnits; i++) {
+ expect(requestObj.ad_units[i].bids.filter((bid) => {
+ return bid.bidder === 'appnexus' || bid.bidder === 'adequant';
+ }).length).to.equal(numBids);
+ }
+ }
+
+ function checkClientCalled(adapter, numBids) {
+ sinon.assert.calledOnce(adapter.callBids);
+ expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids);
+ }
+
+ let TESTING_CONFIG = utils.cloneJson(CONFIG);
+ Object.assign(TESTING_CONFIG, {
+ bidders: ['appnexus', 'adequant'],
+ testing: true
+ });
+ let stubGetSourceBidderMap;
+
+ beforeEach(() => {
+ config.setConfig({s2sConfig: TESTING_CONFIG});
+ AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock;
+ AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock;
+ AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock;
+
+ stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap');
+
+ prebidServerAdapterMock.callBids.reset();
+ adequantAdapterMock.callBids.reset();
+ appnexusAdapterMock.callBids.reset();
+ });
+
+ afterEach(() => {
+ config.setConfig({s2sConfig: {}});
+ s2sTesting.getSourceBidderMap.restore();
+ });
+
+ it('calls server adapter if no sources defined', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: [], [s2sTesting.SERVER]: []});
+ callBids();
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ sinon.assert.notCalled(appnexusAdapterMock.callBids);
+
+ // adequant
+ sinon.assert.notCalled(adequantAdapterMock.callBids);
+ });
+
+ it('calls client adapter if one client source defined', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus'], [s2sTesting.SERVER]: []});
+ callBids();
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 2);
+
+ // adequant
+ sinon.assert.notCalled(adequantAdapterMock.callBids);
+ });
+
+ it('calls client adapters if client sources defined', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ callBids();
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 2);
+
+ // adequant
+ checkClientCalled(adequantAdapterMock, 2);
+ });
+
+ it('calls client adapters if client sources defined', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ callBids();
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 2);
+
+ // adequant
+ checkClientCalled(adequantAdapterMock, 2);
+ });
+
+ it('does not call server adapter for bidders that go to client', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ var adUnits = getTestAdUnits();
+ adUnits[0].bids[0].finalSource = s2sTesting.CLIENT;
+ adUnits[0].bids[1].finalSource = s2sTesting.CLIENT;
+ adUnits[1].bids[0].finalSource = s2sTesting.CLIENT;
+ adUnits[1].bids[1].finalSource = s2sTesting.CLIENT;
+ callBids(adUnits);
+
+ // server adapter
+ sinon.assert.notCalled(prebidServerAdapterMock.callBids);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 2);
+
+ // adequant
+ checkClientCalled(adequantAdapterMock, 2);
+ });
+
+ it('does not call client adapters for bidders that go to server', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ var adUnits = getTestAdUnits();
+ adUnits[0].bids[0].finalSource = s2sTesting.SERVER;
+ adUnits[0].bids[1].finalSource = s2sTesting.SERVER;
+ adUnits[1].bids[0].finalSource = s2sTesting.SERVER;
+ adUnits[1].bids[1].finalSource = s2sTesting.SERVER;
+ callBids(adUnits);
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ sinon.assert.notCalled(appnexusAdapterMock.callBids);
+
+ // adequant
+ sinon.assert.notCalled(adequantAdapterMock.callBids);
+ });
+
+ it('calls client and server adapters for bidders that go to both', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ var adUnits = getTestAdUnits();
+ adUnits[0].bids[0].finalSource = s2sTesting.BOTH;
+ adUnits[0].bids[1].finalSource = s2sTesting.BOTH;
+ adUnits[1].bids[0].finalSource = s2sTesting.BOTH;
+ adUnits[1].bids[1].finalSource = s2sTesting.BOTH;
+ callBids(adUnits);
+
+ // server adapter
+ checkServerCalled(2, 2);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 2);
+
+ // adequant
+ checkClientCalled(adequantAdapterMock, 2);
+ });
+
+ it('makes mixed client/server adapter calls for mixed bidder sources', () => {
+ stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []});
+ var adUnits = getTestAdUnits();
+ adUnits[0].bids[0].finalSource = s2sTesting.CLIENT;
+ adUnits[0].bids[1].finalSource = s2sTesting.CLIENT;
+ adUnits[1].bids[0].finalSource = s2sTesting.SERVER;
+ adUnits[1].bids[1].finalSource = s2sTesting.SERVER;
+ callBids(adUnits);
+
+ // server adapter
+ checkServerCalled(1, 2);
+
+ // appnexus
+ checkClientCalled(appnexusAdapterMock, 1);
+
+ // adequant
+ checkClientCalled(adequantAdapterMock, 1);
+ });
+ });
+
describe('aliasBidderAdaptor', function() {
const CODE = 'sampleBidder';
diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js
index 392b0087099..9cded6fd3e4 100644
--- a/test/spec/unit/pbjs_api_spec.js
+++ b/test/spec/unit/pbjs_api_spec.js
@@ -1590,42 +1590,4 @@ describe('Unit: Prebid Module', function () {
assert.equal($$PREBID_GLOBAL$$.que.push, $$PREBID_GLOBAL$$.cmd.push);
});
});
-
- describe('setS2SConfig', () => {
- let logErrorSpy;
-
- beforeEach(() => {
- logErrorSpy = sinon.spy(utils, 'logError');
- });
-
- afterEach(() => {
- utils.logError.restore();
- });
-
- it('should log error when accountId is missing', () => {
- const options = {
- enabled: true,
- bidders: ['appnexus'],
- timeout: 1000,
- adapter: 'prebidServer',
- endpoint: 'https://prebid.adnxs.com/pbs/v1/auction'
- };
-
- $$PREBID_GLOBAL$$.setConfig({ s2sConfig: {options} });
- assert.ok(logErrorSpy.calledOnce, true);
- });
-
- it('should log error when bidders is missing', () => {
- const options = {
- accountId: '1',
- enabled: true,
- timeout: 1000,
- adapter: 's2s',
- endpoint: 'https://prebid.adnxs.com/pbs/v1/auction'
- };
-
- $$PREBID_GLOBAL$$.setConfig({ s2sConfig: {options} });
- assert.ok(logErrorSpy.calledOnce, true);
- });
- });
});