diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 3bf2ef76623..8fb512ff7e2 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -16,16 +16,21 @@ import find from 'core-js-pure/features/array/find.js'; const getConfig = config.getConfig; const TYPE = S2S.SRC; -let _synced = false; +let _syncCount = 0; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; const DEFAULT_S2S_NETREVENUE = true; -let _s2sConfig; +let _s2sConfigs; /** * @typedef {Object} AdapterOptions * @summary s2sConfig parameter that adds arguments to resulting OpenRTB payload that goes to Prebid Server + * @property {string} adapter + * @property {boolean} enabled + * @property {string} endpoint + * @property {string} syncEndpoint + * @property {number} timeout * @example * // example of multiple bidder configuration * pbjs.setConfig({ @@ -40,11 +45,24 @@ let _s2sConfig; /** * @typedef {Object} S2SDefaultConfig - * @property {boolean} enabled - * @property {number} timeout - * @property {number} maxBids - * @property {string} adapter - * @property {AdapterOptions} adapterOptions + * @summary Base config properties for server to server header bidding + * @property {string} [adapter='prebidServer'] adapter code to use for S2S + * @property {boolean} [enabled=false] enables S2S bidding + * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [maxBids=1] + * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server + * @property {Object} [syncUrlModifier] + */ + +/** + * @typedef {S2SDefaultConfig} S2SConfig + * @summary Configuration for server to server header bidding + * @property {string[]} bidders bidders to request S2S + * @property {string} endpoint endpoint to contact + * @property {string} [defaultVendor] used as key to select the bidder's default config from ßprebidServer/config.js + * @property {boolean} [cacheMarkup] whether to cache the adm result + * @property {string} [syncEndpoint] endpoint URL for syncing cookies + * @property {Object} [extPrebid] properties will be merged into request.ext.prebid */ /** @@ -64,30 +82,19 @@ config.setDefaults({ }); /** - * 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 {number} [defaultTtl] ttl for S2S bidders when pbs does not return a ttl on the response - defaults to 60` - * @property {boolean} [cacheMarkup] whether to cache the adm result - * @property {string} [adapter] adapter code to use for S2S - * @property {string} [syncEndpoint] endpoint URL for syncing cookies - * @property {Object} [extPrebid] properties will be merged into request.ext.prebid - * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server + * @param {S2SConfig} option + * @return {boolean} */ -function setS2sConfig(options) { - if (options.defaultVendor) { - let vendor = options.defaultVendor; - let optionKeys = Object.keys(options); +function updateConfigDefaultVendor(option) { + if (option.defaultVendor) { + let vendor = option.defaultVendor; + let optionKeys = Object.keys(option); if (S2S_VENDORS[vendor]) { // vendor keys will be set if either: the key was not specified by user // or if the user did not set their own distinct value (ie using the system default) to override the vendor Object.keys(S2S_VENDORS[vendor]).forEach((vendorKey) => { - if (s2sDefaultConfig[vendorKey] === options[vendorKey] || !includes(optionKeys, vendorKey)) { - options[vendorKey] = S2S_VENDORS[vendor][vendorKey]; + if (s2sDefaultConfig[vendorKey] === option[vendorKey] || !includes(optionKeys, vendorKey)) { + option[vendorKey] = S2S_VENDORS[vendor][vendorKey]; } }); } else { @@ -95,9 +102,14 @@ function setS2sConfig(options) { return false; } } +} - let keys = Object.keys(options); - +/** + * @param {S2SConfig} option + * @return {boolean} + */ +function validateConfigRequiredProps(option) { + const keys = Object.keys(option); if (['accountId', 'bidders', 'endpoint'].filter(key => { if (!includes(keys, key)) { utils.logError(key + ' missing in server to server config'); @@ -105,10 +117,43 @@ function setS2sConfig(options) { } return false; }).length > 0) { + return false; + } +} + +/** + * @param {(S2SConfig[]|S2SConfig)} options + */ +function setS2sConfig(options) { + if (!options) { return; } + const normalizedOptions = Array.isArray(options) ? options : [options]; + + const activeBidders = []; + const optionsValid = normalizedOptions.every((option, i, array) => { + const updateSuccess = updateConfigDefaultVendor(option); + if (updateSuccess !== false) { + const valid = validateConfigRequiredProps(option); + if (valid !== false) { + if (Array.isArray(option['bidders'])) { + array[i]['bidders'] = option['bidders'].filter(bidder => { + if (activeBidders.indexOf(bidder) === -1) { + activeBidders.push(bidder); + return true; + } + return false; + }); + } + return true; + } + } + return false; + }); - _s2sConfig = options; + if (optionsValid) { + return _s2sConfigs = normalizedOptions; + } } getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); @@ -116,25 +161,25 @@ getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); * resets the _synced variable back to false, primiarily used for testing purposes */ export function resetSyncedStatus() { - _synced = false; + _syncCount = 0; } /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent, uspConsent) { - if (_synced) { +function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { + if (_s2sConfigs.length === _syncCount) { return; } - _synced = true; + _syncCount++; const payload = { uuid: utils.generateUUID(), bidders: bidderCodes, - account: _s2sConfig.accountId + account: s2sConfig.accountId }; - let userSyncLimit = _s2sConfig.userSyncLimit; + let userSyncLimit = s2sConfig.userSyncLimit; if (utils.isNumber(userSyncLimit) && userSyncLimit > 0) { payload['limit'] = userSyncLimit; } @@ -156,11 +201,11 @@ function queueSync(bidderCodes, gdprConsent, uspConsent) { } const jsonPayload = JSON.stringify(payload); - ajax(_s2sConfig.syncEndpoint, + ajax(s2sConfig.syncEndpoint, (response) => { try { response = JSON.parse(response); - doAllSyncs(response.bidder_status); + doAllSyncs(response.bidder_status, s2sConfig); } catch (e) { utils.logError(e); } @@ -172,16 +217,16 @@ function queueSync(bidderCodes, gdprConsent, uspConsent) { }); } -function doAllSyncs(bidders) { +function doAllSyncs(bidders, s2sConfig) { if (bidders.length === 0) { return; } const thisSync = bidders.pop(); if (thisSync.no_cookie) { - doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, utils.bind.call(doAllSyncs, null, bidders)); + doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, utils.bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); } else { - doAllSyncs(bidders); + doAllSyncs(bidders, s2sConfig); } } @@ -192,10 +237,11 @@ function doAllSyncs(bidders) { * @param {string} url the url to sync * @param {string} bidder name of bidder doing sync for * @param {function} done an exit callback; to signify this pixel has either: finished rendering or something went wrong + * @param {S2SConfig} s2sConfig */ -function doPreBidderSync(type, url, bidder, done) { - if (_s2sConfig.syncUrlModifier && typeof _s2sConfig.syncUrlModifier[bidder] === 'function') { - const newSyncUrl = _s2sConfig.syncUrlModifier[bidder](type, url, bidder); +function doPreBidderSync(type, url, bidder, done, s2sConfig) { + if (s2sConfig.syncUrlModifier && typeof s2sConfig.syncUrlModifier[bidder] === 'function') { + const newSyncUrl = s2sConfig.syncUrlModifier[bidder](type, url, bidder); doBidderSync(type, newSyncUrl, bidder, done) } else { doBidderSync(type, url, bidder, done) @@ -240,13 +286,13 @@ function doClientSideSyncs(bidders) { }); } -function _appendSiteAppDevice(request, pageUrl) { +function _appendSiteAppDevice(request, pageUrl, accountId) { if (!request) return; // ORTB specifies app OR site if (typeof config.getConfig('app') === 'object') { request.app = config.getConfig('app'); - request.app.publisher = {id: _s2sConfig.accountId} + request.app.publisher = {id: accountId} } else { request.site = {}; if (utils.isPlainObject(config.getConfig('site'))) { @@ -254,7 +300,7 @@ function _appendSiteAppDevice(request, pageUrl) { } // set publisher.id if not already defined if (!utils.deepAccess(request.site, 'publisher.id')) { - utils.deepSetValue(request.site, 'publisher.id', _s2sConfig.accountId); + utils.deepSetValue(request.site, 'publisher.id', accountId); } // set site.page if not already defined if (!request.site.page) { @@ -409,7 +455,7 @@ export function resetWurlMap() { } const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits) { + buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig) { let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; @@ -555,11 +601,11 @@ const OPEN_RTB_PROTOCOL = { if (adapter && adapter.getSpec().transformBidParams) { bid.params = adapter.getSpec().transformBidParams(bid.params, true); } - acc[bid.bidder] = (_s2sConfig.adapterOptions && _s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, _s2sConfig.adapterOptions[bid.bidder]) : bid.params; + acc[bid.bidder] = (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params; return acc; }, {}); - const imp = { id: adUnit.code, ext, secure: _s2sConfig.secure }; + const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; /** * Prebid AdSlot @@ -601,7 +647,7 @@ const OPEN_RTB_PROTOCOL = { const request = { id: s2sBidRequest.tid, source: {tid: s2sBidRequest.tid}, - tmax: _s2sConfig.timeout, + tmax: s2sConfig.timeout, imp: imps, test: getConfig('debug') ? 1 : 0, ext: { @@ -619,8 +665,8 @@ const OPEN_RTB_PROTOCOL = { }; // s2sConfig video.ext.prebid is passed through openrtb to PBS - if (_s2sConfig.extPrebid && typeof _s2sConfig.extPrebid === 'object') { - request.ext.prebid = Object.assign(request.ext.prebid, _s2sConfig.extPrebid); + if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { + request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); } /** @@ -635,7 +681,7 @@ const OPEN_RTB_PROTOCOL = { request.cur = [adServerCur[0]]; } - _appendSiteAppDevice(request, firstBidRequest.refererInfo.referer); + _appendSiteAppDevice(request, bidRequests[0].refererInfo.referer, s2sConfig.accountId); // pass schain object if it is present const schain = utils.deepAccess(bidRequests, '0.bids.0.schain'); @@ -690,7 +736,7 @@ const OPEN_RTB_PROTOCOL = { return request; }, - interpretResponse(response, bidderRequests) { + interpretResponse(response, bidderRequests, s2sConfig) { const bids = []; [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] @@ -847,8 +893,8 @@ const OPEN_RTB_PROTOCOL = { bidObject.meta = bidObject.meta || {}; if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - const configTtl = _s2sConfig.defaultTtl || DEFAULT_S2S_TTL; // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). + const configTtl = s2sConfig.defaultTtl || DEFAULT_S2S_TTL; bidObject.ttl = (bid.exp) ? bid.exp : configTtl; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; @@ -897,37 +943,39 @@ export function PrebidServer() { .reduce(utils.flatten) .filter(utils.uniques); - if (_s2sConfig && _s2sConfig.syncEndpoint) { - let gdprConsent, uspConsent; - if (Array.isArray(bidRequests) && bidRequests.length > 0) { - gdprConsent = bidRequests[0].gdprConsent; - uspConsent = bidRequests[0].uspConsent; - } + if (Array.isArray(_s2sConfigs)) { + if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint) { + let gdprConsent, uspConsent; + if (Array.isArray(bidRequests) && bidRequests.length > 0) { + gdprConsent = bidRequests[0].gdprConsent; + uspConsent = bidRequests[0].uspConsent; + } - let syncBidders = _s2sConfig.bidders - .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) - .filter((bidder, index, array) => (array.indexOf(bidder) === index)); + let syncBidders = s2sBidRequest.s2sConfig.bidders + .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) + .filter((bidder, index, array) => (array.indexOf(bidder) === index)); - queueSync(syncBidders, gdprConsent, uspConsent); - } + queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); + } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits); - const requestJson = request && JSON.stringify(request); - if (request && requestJson) { - ajax( - _s2sConfig.endpoint, - { - success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done), - error: done - }, - requestJson, - { contentType: 'text/plain', withCredentials: true } - ); + const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig); + const requestJson = request && JSON.stringify(request); + if (request && requestJson) { + ajax( + s2sBidRequest.s2sConfig.endpoint, + { + success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done, s2sBidRequest.s2sConfig), + error: done + }, + requestJson, + { contentType: 'text/plain', withCredentials: true } + ); + } } }; /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done) { + function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done, s2sConfig) { let result; let bids = []; @@ -937,7 +985,7 @@ export function PrebidServer() { bids = OPEN_RTB_PROTOCOL.interpretResponse( result, bidderRequests, - requestedBidders + s2sConfig ); bids.forEach(({adUnit, bid}) => { diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 98b3b86671c..1f2bb473174 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -1,4 +1,3 @@ -import { config } from '../src/config.js'; import { setS2STestingModule } from '../src/adapterManager.js'; let s2sTesting = {}; @@ -9,39 +8,31 @@ const CLIENT = 'client'; s2sTesting.SERVER = SERVER; s2sTesting.CLIENT = CLIENT; -var testing = false; // whether testing is turned on -var bidSource = {}; // store bidder sources determined from s2sConfing bidderControl +s2sTesting.bidSource = {}; // store bidder sources determined from s2sConfig bidderControl s2sTesting.globalRand = Math.random(); // if 10% of bidderA and 10% of bidderB should be server-side, make it the same 10% -// load s2sConfig -config.getConfig('s2sConfig', config => { - testing = config.s2sConfig && config.s2sConfig.testing; - s2sTesting.calculateBidSources(config.s2sConfig); -}); - -s2sTesting.getSourceBidderMap = function(adUnits = []) { +s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { var sourceBidders = {[SERVER]: {}, [CLIENT]: {}}; - // bail if testing is not turned on - if (!testing) { - return {[SERVER]: [], [CLIENT]: []}; - } - adUnits.forEach((adUnit) => { // if any adUnit bidders specify a bidSource, include them (adUnit.bids || []).forEach((bid) => { + // When a s2sConfig does not have testing=true and did not calc bid sources + if (allS2SBidders.indexOf(bid.bidder) > -1 && !s2sTesting.bidSource[bid.bidder]) { + s2sTesting.bidSource[bid.bidder] = SERVER; + } // calculate the source once and store on bid object bid.calcSource = bid.calcSource || s2sTesting.getSource(bid.bidSource); // if no bidSource at bid level, default to bidSource from bidder - bid.finalSource = bid.calcSource || bidSource[bid.bidder] || CLIENT; // default to client + bid.finalSource = bid.calcSource || s2sTesting.bidSource[bid.bidder] || CLIENT; // default to client // add bidder to sourceBidders data structure sourceBidders[bid.finalSource][bid.bidder] = true; }); }); // make sure all bidders in bidSource are in sourceBidders - Object.keys(bidSource).forEach((bidder) => { - sourceBidders[bidSource[bidder]][bidder] = true; + Object.keys(s2sTesting.bidSource).forEach((bidder) => { + sourceBidders[s2sTesting.bidSource[bidder]][bidder] = true; }); // return map of source => array of bidders @@ -53,18 +44,14 @@ s2sTesting.getSourceBidderMap = function(adUnits = []) { /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level - * @param s2sConfig server-to-server configuration + * @param s2sConfigs server-to-server configuration */ s2sTesting.calculateBidSources = function(s2sConfig = {}) { - // bail if testing is not turned on - if (!testing) { - return; - } - bidSource = {}; // reset bid sources // calculate bid source (server/client) for each s2s bidder + var bidderControl = s2sConfig.bidderControl || {}; (s2sConfig.bidders || []).forEach((bidder) => { - bidSource[bidder] = s2sTesting.getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server + s2sTesting.bidSource[bidder] = s2sTesting.getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server }); }; diff --git a/src/adapterManager.js b/src/adapterManager.js index aa67fd333c2..76e0f5404a1 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -22,9 +22,11 @@ let adapterManager = {}; let _bidderRegistry = adapterManager.bidderRegistry = {}; let _aliasRegistry = adapterManager.aliasRegistry = {}; -let _s2sConfig = {}; +let _s2sConfigs = []; config.getConfig('s2sConfig', config => { - _s2sConfig = config.s2sConfig; + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } }); var _analyticsRegistry = {}; @@ -118,15 +120,15 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) const hookedGetBids = hook('sync', getBids, 'getBids'); -function getAdUnitCopyForPrebidServer(adUnits) { - let adaptersServerSide = _s2sConfig.bidders; +function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { + let adaptersServerSide = s2sConfig.bidders; let adUnitsCopy = utils.deepClone(adUnits); adUnitsCopy.forEach((adUnit) => { // filter out client side bids adUnit.bids = adUnit.bids.filter((bid) => { return includes(adaptersServerSide, bid.bidder) && - (!doingS2STesting() || bid.finalSource !== s2sTestingModule.CLIENT); + (!doingS2STesting(s2sConfig) || bid.finalSource !== s2sTestingModule.CLIENT); }).map((bid) => { bid.bid_id = utils.getUniqueIdentifierStr(); return bid; @@ -145,7 +147,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { // filter out s2s bids adUnitsClientCopy.forEach((adUnit) => { adUnit.bids = adUnit.bids.filter((bid) => { - return !doingS2STesting() || bid.finalSource !== s2sTestingModule.SERVER; + return !clientTestAdapters.length || bid.finalSource !== s2sTestingModule.SERVER; }) }); @@ -177,6 +179,21 @@ export let uspDataHandler = { } }; +// export for testing +export let clientTestAdapters = []; +export const allS2SBidders = []; + +export function getAllS2SBidders() { + adapterManager.s2STestingEnabled = false; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + allS2SBidders.push(...s2sConfig.bidders); + } + } + }) +} + adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { /** * emit and pass adunits for external modification @@ -184,8 +201,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); - let bidRequests = []; - let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { bidderCodes = shuffle(bidderCodes); @@ -193,70 +208,86 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a const refererInfo = getRefererInfo(); let clientBidderCodes = bidderCodes; - let clientTestAdapters = []; - - if (_s2sConfig.enabled) { - // if s2sConfig.bidderControl testing is turned on - if (doingS2STesting()) { - // get all adapters doing client testing - const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits); - clientTestAdapters = bidderMap[s2sTestingModule.CLIENT]; - } - - // these are called on the s2s adapter - let adaptersServerSide = _s2sConfig.bidders; - // don't call these client side (unless client request is needed for testing) - clientBidderCodes = bidderCodes.filter(elm => - !includes(adaptersServerSide, elm) || includes(clientTestAdapters, elm) - ); + let bidRequests = []; - const adUnitsContainServerRequests = adUnits => Boolean( - find(adUnits, adUnit => find(adUnit.bids, bid => ( - bid.bidSource || - (_s2sConfig.bidderControl && _s2sConfig.bidderControl[bid.bidder]) - ) && bid.finalSource === s2sTestingModule.SERVER)) - ); + if (allS2SBidders.length === 0) { + getAllS2SBidders(); + } - if (isTestingServerOnly() && adUnitsContainServerRequests(adUnits)) { - clientBidderCodes.length = 0; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (doingS2STesting(s2sConfig)) { + s2sTestingModule.calculateBidSources(s2sConfig); + const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits, allS2SBidders); + // get all adapters doing client testing + bidderMap[s2sTestingModule.CLIENT].forEach(bidder => { + if (!includes(clientTestAdapters, bidder)) { + clientTestAdapters.push(bidder); + } + }) + } } + }) - let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits); - let tid = utils.generateUUID(); - adaptersServerSide.forEach(bidderCode => { - const bidderRequestId = utils.getUniqueIdentifierStr(); - const bidderRequest = { - bidderCode, - auctionId, - bidderRequestId, - tid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), - auctionStart: auctionStart, - timeout: _s2sConfig.timeout, - src: CONSTANTS.S2S.SRC, - refererInfo - }; - if (bidderRequest.bids.length !== 0) { - bidRequests.push(bidderRequest); + // don't call these client side (unless client request is needed for testing) + clientBidderCodes = bidderCodes.filter(bidderCode => { + return !includes(allS2SBidders, bidderCode) || includes(clientTestAdapters, bidderCode) + }); + + // these are called on the s2s adapter + let adaptersServerSide = allS2SBidders; + + const adUnitsContainServerRequests = (adUnits, s2sConfig) => Boolean( + find(adUnits, adUnit => find(adUnit.bids, bid => ( + bid.bidSource || + (s2sConfig.bidderControl && s2sConfig.bidderControl[bid.bidder]) + ) && bid.finalSource === s2sTestingModule.SERVER)) + ); + + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if ((isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig))) { + utils.logWarn('testServerOnly: True. All client requests will be suppressed.'); + clientBidderCodes.length = 0; } - }); - // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() - // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs - adUnitsS2SCopy.forEach((adUnitCopy) => { - let validBids = adUnitCopy.bids.filter((adUnitBid) => { - return find(bidRequests, request => { - return find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id); - }); + let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); + let tid = utils.generateUUID(); + adaptersServerSide.forEach(bidderCode => { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + auctionId, + bidderRequestId, + tid, + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + auctionStart: auctionStart, + timeout: s2sConfig.timeout, + src: CONSTANTS.S2S.SRC, + refererInfo + }; + if (bidderRequest.bids.length !== 0) { + bidRequests.push(bidderRequest); + } }); - adUnitCopy.bids = validBids; - }); - bidRequests.forEach(request => { - request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); - }); - } + // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() + // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs + adUnitsS2SCopy.forEach((adUnitCopy) => { + let validBids = adUnitCopy.bids.filter((adUnitBid) => + find(bidRequests, request => + find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id))); + adUnitCopy.bids = validBids; + }); + + bidRequests.forEach(request => { + if (request.adUnitsS2SCopy === undefined) { + request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + } + }); + } + }) // client adapters let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); @@ -306,56 +337,74 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request return partitions; }, [[], []]); - if (serverBidRequests.length) { - // s2s should get the same client side timeout as other client side requests. - const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { - request: requestCallbacks.request.bind(null, 's2s'), - done: requestCallbacks.done - } : undefined); - let adaptersServerSide = _s2sConfig.bidders; - const s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; - let tid = serverBidRequests[0].tid; - let adUnitsS2SCopy = serverBidRequests[0].adUnitsS2SCopy; - - if (s2sAdapter) { - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; - if (s2sBidRequest.ad_units.length) { - let doneCbs = serverBidRequests.map(bidRequest => { - bidRequest.start = timestamp(); - return doneCb.bind(bidRequest); - }); - - // 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.bidder) }, [])); - }, []); - utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => { - return includes(allBidders, adapter); - }).join(',')}`); - - // fire BID_REQUESTED event for each s2s bidRequest - serverBidRequests.forEach(bidRequest => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - }); + var uniqueServerBidRequests = []; + serverBidRequests.forEach(serverBidRequest => { + var index = -1; + for (var i = 0; i < uniqueServerBidRequests.length; ++i) { + if (serverBidRequest.tid === uniqueServerBidRequests[i].tid) { + index = i; + break; + } + } + if (index <= -1) { + uniqueServerBidRequests.push(serverBidRequest); + } + }); - // make bid requests - s2sAdapter.callBids( - s2sBidRequest, - serverBidRequests, - function(adUnitCode, bid) { - let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); - if (bidderRequest) { - addBidResponse.call(bidderRequest, adUnitCode, bid) - } - }, - () => doneCbs.forEach(done => done()), - s2sAjax - ); + let counter = 0 + _s2sConfigs.forEach((s2sConfig) => { + if (s2sConfig && uniqueServerBidRequests[counter] && includes(s2sConfig.bidders, uniqueServerBidRequests[counter].bidderCode)) { + // s2s should get the same client side timeout as other client side requests. + const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { + request: requestCallbacks.request.bind(null, 's2s'), + done: requestCallbacks.done + } : undefined); + let adaptersServerSide = s2sConfig.bidders; + const s2sAdapter = _bidderRegistry[s2sConfig.adapter]; + let tid = uniqueServerBidRequests[counter].tid; + let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; + + let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.tid === tid) + + if (s2sAdapter) { + let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + if (s2sBidRequest.ad_units.length) { + let doneCbs = uniqueServerRequests.map(bidRequest => { + bidRequest.start = timestamp(); + return doneCb.bind(bidRequest); + }); + + // only log adapters that actually have adUnit bids + let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { + return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => adapters.concat(bid.bidder), [])); + }, []); + utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => includes(allBidders, adapter)).join(',')}`); + + // fire BID_REQUESTED event for each s2s bidRequest + uniqueServerRequests.forEach(bidRequest => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + }); + + // make bid requests + s2sAdapter.callBids( + s2sBidRequest, + serverBidRequests, + (adUnitCode, bid) => { + let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); + if (bidderRequest) { + addBidResponse.call(bidderRequest, adUnitCode, bid) + } + }, + () => doneCbs.forEach(done => done()), + s2sAjax + ); + } + } else { + utils.logError('missing ' + s2sConfig.adapter); } - } else { - utils.logError('missing ' + _s2sConfig.adapter); + counter++ } - } + }); // handle client adapter requests clientBidRequests.forEach(bidRequest => { @@ -390,12 +439,12 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request }); }; -function doingS2STesting() { - return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule; +function doingS2STesting(s2sConfig) { + return s2sConfig && s2sConfig.enabled && s2sConfig.testing && s2sTestingModule; } -function isTestingServerOnly() { - return Boolean(doingS2STesting() && _s2sConfig.testServerOnly); +function isTestingServerOnly(s2sConfig) { + return Boolean(doingS2STesting(s2sConfig) && s2sConfig.testServerOnly); }; function getSupportedMediaTypes(bidderCode) { @@ -433,14 +482,20 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { let bidAdaptor = _bidderRegistry[bidderCode]; if (typeof bidAdaptor === 'undefined') { // check if alias is part of s2sConfig and allow them to register if so (as base bidder may be s2s-only) - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - - if (!(s2sBidders && includes(s2sBidders, alias))) { + const nonS2SAlias = []; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + const s2sBidders = s2sConfig && s2sConfig.bidders; + if (!(s2sConfig && includes(s2sBidders, alias))) { + nonS2SAlias.push(bidderCode); + } else { + _aliasRegistry[alias] = bidderCode; + } + } + }); + nonS2SAlias.forEach(bidderCode => { utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); - } else { - _aliasRegistry[alias] = bidderCode; - } + }) } else { try { let newAdapter; diff --git a/src/prebid.js b/src/prebid.js index 0f72ca878e5..3452107effd 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -488,6 +488,18 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + let _s2sConfigs = []; + const s2sBidders = []; + config.getConfig('s2sConfig', config => { + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } + }); + + _s2sConfigs.forEach(s2sConfig => { + s2sBidders.push(...s2sConfig.bidders); + }); + adUnits = checkAdUnitSetup(adUnits); if (adUnitCodes && adUnitCodes.length) { @@ -512,11 +524,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - const bidders = (s2sBidders) ? allBidders.filter(bidder => { - return !includes(s2sBidders, bidder); - }) : allBidders; + const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; adUnit.transactionId = utils.generateUUID(); diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 2a0a7638fc4..908382f8daa 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -676,7 +676,13 @@ export function getAdUnits() { 'bidder': 'appnexus', 'params': { 'placementId': '543221', - 'test': 'me' + } + }, + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': 1234567, + 'adSlot': '1234567@728x90' } } ] diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 17c0fc290c6..f17cd3ab14f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -26,6 +26,7 @@ const REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '0.30.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -80,6 +81,7 @@ const VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -110,6 +112,7 @@ const OUTSTREAM_VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -162,7 +165,7 @@ const OUTSTREAM_VIDEO_REQUEST = { } } } - ] + ], }; let BID_REQUESTS; @@ -497,11 +500,9 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should not add outstream without renderer', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('should not add outstrean without renderer', function () { + config.setConfig({ s2sConfig: CONFIG }); - config.setConfig({ s2sConfig: ortb2Config }); adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); @@ -535,10 +536,7 @@ describe('S2S Adapter', function () { }); it('adds gdpr consent information to ortb2 request depending on presence of module', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -564,10 +562,7 @@ describe('S2S Adapter', function () { }); it('adds additional consent information to ortb2 request depending on presence of module', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -608,7 +603,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(1); @@ -624,13 +622,16 @@ describe('S2S Adapter', function () { let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: 'xyz789abcc', gdprApplies: false }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(0); @@ -650,7 +651,10 @@ describe('S2S Adapter', function () { gdprApplies: undefined }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.undefined; @@ -664,9 +668,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1NYN'; @@ -693,7 +695,10 @@ describe('S2S Adapter', function () { let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1YNN'; - adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, uspBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -708,9 +713,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let consentBidRequest = utils.deepClone(BID_REQUESTS); consentBidRequest[0].uspConsent = '1NYN'; @@ -748,7 +751,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig + + adapter.callBids(s2sBidRequest, consentBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -806,11 +812,8 @@ describe('S2S Adapter', function () { }); it('adds debugging value from storedAuctionResponse to OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' } }; @@ -827,12 +830,8 @@ describe('S2S Adapter', function () { }); it('adds device.w and device.h even if the config lacks a device object', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, app: { bundle: 'com.test.app' }, }; @@ -850,12 +849,8 @@ describe('S2S Adapter', function () { }); it('adds native request for OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig + s2sConfig: CONFIG }; config.setConfig(_config); @@ -909,12 +904,8 @@ describe('S2S Adapter', function () { }); it('adds site if app is not present', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, site: { publisher: { id: '1234', @@ -949,10 +940,7 @@ describe('S2S Adapter', function () { }); it('adds appnexus aliases to request', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig: s2sConfig }); + config.setConfig({ s2sConfig: CONFIG }); const aliasBidder = { bidder: 'brealtime', @@ -981,10 +969,7 @@ describe('S2S Adapter', function () { }); it('adds dynamic aliases to request', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig: s2sConfig }); + config.setConfig({ s2sConfig: CONFIG }); const alias = 'foobar'; const aliasBidder = { @@ -1110,10 +1095,13 @@ describe('S2S Adapter', function () { cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; cookieSyncConfig.userSyncLimit = 1; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + config.setConfig({ s2sConfig: cookieSyncConfig }); let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1126,8 +1114,11 @@ describe('S2S Adapter', function () { cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1138,8 +1129,11 @@ describe('S2S Adapter', function () { config.resetConfig(); config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest2 = utils.deepClone(REQUEST); + s2sBidRequest2.s2sConfig = cookieSyncConfig; + bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest2, bidRequest, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1149,7 +1143,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig adapterOptions to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1162,8 +1155,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].ext.appnexus).to.haveOwnProperty('key'); expect(requestBid.imp[0].ext.appnexus.key).to.be.equal('value') @@ -1171,7 +1167,6 @@ describe('S2S Adapter', function () { describe('config site value is added to the oRTB request', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1183,6 +1178,9 @@ describe('S2S Adapter', function () { ip: '75.97.0.47' }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + it('and overrides publisher and page', function () { config.setConfig({ s2sConfig: s2sConfig, @@ -1193,7 +1191,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1211,7 +1210,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1223,10 +1223,7 @@ describe('S2S Adapter', function () { }); it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; + let consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); let userIdBidRequest = utils.deepClone(BID_REQUESTS); @@ -1274,11 +1271,8 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: ['USD', 'GB', 'UK', 'AU'] }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1289,11 +1283,8 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: 'NZ' }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1304,9 +1295,7 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' is unset: ORTB should not define a \'cur\' property', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: s2sConfig }); + config.setConfig({ s2sConfig: CONFIG }); const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); @@ -1317,7 +1306,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includebidderkeys: false for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1331,7 +1319,11 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includebidderkeys'); @@ -1340,7 +1332,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includewinners: true for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1352,9 +1343,12 @@ describe('S2S Adapter', function () { device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includewinners'); @@ -1363,7 +1357,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig video.ext.prebid to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { foo: 'bar' } @@ -1374,8 +1367,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); @@ -1392,7 +1388,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { targeting: { includewinners: false, @@ -1406,8 +1401,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); @@ -1423,7 +1421,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' @@ -1440,8 +1437,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); @@ -1529,9 +1529,7 @@ describe('S2S Adapter', function () { describe('pbAdSlot config', function () { it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1544,9 +1542,7 @@ describe('S2S Adapter', function () { }); it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = {}; @@ -1560,9 +1556,7 @@ describe('S2S Adapter', function () { }); it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { @@ -1580,9 +1574,7 @@ describe('S2S Adapter', function () { }); it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { @@ -1603,9 +1595,7 @@ describe('S2S Adapter', function () { describe('GAM ad unit config', function () { it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1618,9 +1608,7 @@ describe('S2S Adapter', function () { }); it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = {}; @@ -1634,9 +1622,7 @@ describe('S2S Adapter', function () { }); it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { @@ -1656,9 +1642,7 @@ describe('S2S Adapter', function () { }); it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { @@ -1831,10 +1815,7 @@ describe('S2S Adapter', function () { }; sinon.stub(adapterManager, 'getBidAdapter').returns(rubiconAdapter); - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1845,10 +1826,7 @@ describe('S2S Adapter', function () { }); it('handles OpenRTB responses and call BIDDER_DONE', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1874,12 +1852,13 @@ describe('S2S Adapter', function () { it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', defaultTtl: 30 }); - config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(events.emit); @@ -1895,7 +1874,10 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); sinon.assert.calledOnce(addBidResponse); @@ -1923,7 +1905,10 @@ describe('S2S Adapter', function () { } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1948,7 +1933,11 @@ describe('S2S Adapter', function () { cacheResponse.seatbid.forEach(item => { item.bid[0].ext.prebid.targeting = targetingTestData }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1974,7 +1963,11 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -2001,7 +1994,10 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -2027,7 +2023,11 @@ describe('S2S Adapter', function () { hb_bidid: '1234567890', } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -2047,7 +2047,10 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -2067,7 +2070,10 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); sinon.assert.calledOnce(addBidResponse); @@ -2368,10 +2374,11 @@ describe('S2S Adapter', function () { s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; s2sConfig.bidders = ['appnexus', 'rubicon-alias']; - const request = utils.deepClone(REQUEST); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; // Add another bidder, `rubicon-alias` - request.ad_units[0].bids.push({ + s2sBidRequest.ad_units[0].bids.push({ bidder: 'rubicon-alias', params: { accoundId: 14062, @@ -2383,8 +2390,6 @@ describe('S2S Adapter', function () { // create an alias for the Rubicon Bid Adapter adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); - config.setConfig({ s2sConfig }); - const bidRequest = utils.deepClone(BID_REQUESTS); bidRequest.push({ 'bidderCode': 'rubicon-alias', @@ -2428,7 +2433,7 @@ describe('S2S Adapter', function () { 'src': 's2s' }); - adapter.callBids(request, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index 5c7f3004dee..273f6747e52 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,5 +1,4 @@ import s2sTesting from 'modules/s2sTesting.js'; -import { config } from 'src/config.js'; var expect = require('chai').expect; @@ -78,26 +77,16 @@ describe('s2sTesting', function () { beforeEach(function () { // set random number for testing s2sTesting.globalRand = 0.7; - }); - - it('does not work if testing is "false"', function () { - config.setConfig({s2sConfig: { - bidders: ['rubicon'], - testing: false, - bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); - expect(s2sTesting.getSourceBidderMap()).to.eql({ - server: [], - client: [] - }); + s2sTesting.bidSource = {}; }); it('sets one client bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); + }; + + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: [], client: ['rubicon'] @@ -105,11 +94,11 @@ describe('s2sTesting', function () { }); it('sets one server bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 4, client: 1}}} - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -117,10 +106,10 @@ describe('s2sTesting', function () { }); it('defaults to server', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -128,13 +117,14 @@ describe('s2sTesting', function () { }); it('sets two bidders', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 3, client: 1}}, appnexus: {bidSource: {server: 1, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(); expect(serverClientBidders.server).to.eql(['rubicon']); expect(serverClientBidders.client).to.have.members(['appnexus']); @@ -143,33 +133,37 @@ describe('s2sTesting', function () { it('sends both bidders to same source when weights are the same', function () { s2sTesting.globalRand = 0.01; - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 1, client: 99}}, appnexus: {bidSource: {server: 1, client: 99}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); - config.setConfig({s2sConfig: { + const s2sConfig2 = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 99, client: 1}}, appnexus: {bidSource: {server: 99, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig2); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon', 'appnexus'], client: [] @@ -186,11 +180,12 @@ describe('s2sTesting', function () { }); describe('setting source through adUnits', function () { + const s2sConfig3 = {testing: true}; + beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; + s2sTesting.bidSource = {}; }); it('sets one bidder source from one adUnit', function () { @@ -199,7 +194,8 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 4, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: ['rubicon'], client: [] }); @@ -212,7 +208,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 1, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -227,7 +223,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -243,7 +239,7 @@ describe('s2sTesting', function () { {bidder: 'appnexus', bidSource: {server: 3, client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.eql(['appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon']); // should have saved the source on the bid @@ -264,7 +260,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['rubicon']); expect(serverClientBidders.server).to.not.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus', 'bidder3']); @@ -287,7 +283,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', calcSource: 'server', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.server).to.not.have.members(['rubicon']); @@ -305,8 +301,6 @@ describe('s2sTesting', function () { describe('setting source through s2sconfig and adUnits', function () { beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; }); @@ -321,15 +315,15 @@ describe('s2sTesting', function () { ]; // set rubicon: client and appnexus: server - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], testing: true, bidderControl: { rubicon: {bidSource: {server: 2, client: 1}}, appnexus: {bidSource: {server: 1}} } - }}); - + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); expect(serverClientBidders.server).to.have.members(['rubicon', 'appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus']); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 9ccdd6aef59..00a0579e676 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import adapterManager, { allS2SBidders, clientTestAdapters, gdprDataHandler } from 'src/adapterManager.js'; import { getAdUnits, getServerTestingConfig, @@ -25,6 +25,17 @@ const CONFIG = { bidders: ['appnexus'], accountId: 'abc' }; + +const CONFIG2 = { + enabled: true, + endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + timeout: 1000, + maxBids: 1, + adapter: 'prebidServer', + bidders: ['pubmatic'], + accountId: 'def' +} + var prebidServerAdapterMock = { bidder: 'prebidServer', callBids: sinon.stub() @@ -43,6 +54,11 @@ var rubiconAdapterMock = { callBids: sinon.stub() }; +var pubmaticAdapterMock = { + bidder: 'rubicon', + callBids: sinon.stub() +}; + var badAdapterMock = { bidder: 'badBidder', callBids: sinon.stub().throws(Error('some fake error')) @@ -82,6 +98,7 @@ describe('adapterManager tests', function () { adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; adapterManager.bidderRegistry['badBidder'] = badAdapterMock; + adapterManager.bidderRegistry['badBidder'] = badAdapterMock; }); afterEach(function () { @@ -394,144 +411,144 @@ describe('adapterManager tests', function () { prebidServerAdapterMock.callBids.reset(); }); - it('invokes callBids on the S2S adapter', function () { - let bidRequests = [{ - 'bidderCode': 'appnexus', - 'auctionId': '1863e370099523', - 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', - 'timeout': 1000, - 'src': 's2s', - 'adUnitsS2SCopy': [ - { - 'code': '/19968336/header-bid-tag1', - 'sizes': [ - { - 'w': 728, - 'h': 90 + const bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' }, - { - 'w': 970, - 'h': 90 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '543221', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 ], - 'bidId': '68136e1c47023d', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220995, - 'status': 1, - 'bid_id': '68136e1c47023d' - } - ] - }, - { - 'code': '/19968336/header-bid-tag-0', - 'sizes': [ - { - 'w': 300, - 'h': 250 + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' }, - { - 'w': 300, - 'h': 600 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '5324321' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 ], - 'bidId': '7e5d6af25ed188', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220996, - 'bid_id': '7e5d6af25ed188' - } - ] - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 + [ + 300, + 600 + ] ], - [ - 970, - 90 - ] + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 ], - 'bidId': '392b5a6b05d648', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1, - 'transactionId': 'fsafsa' + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' }, - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 ], - 'bidId': '4dccdc37746135', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897463, - 'status': 1, - 'transactionId': 'fsafsa' - } - ], - 'start': 1462918897460 - }]; + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }]; + it('invokes callBids on the S2S adapter', function () { adapterManager.callBids( getAdUnits(), bidRequests, @@ -559,143 +576,6 @@ describe('adapterManager tests', function () { ] }); - let bidRequests = [{ - 'bidderCode': 'appnexus', - 'auctionId': '1863e370099523', - 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', - 'src': 's2s', - 'timeout': 1000, - 'adUnitsS2SCopy': [ - { - 'code': '/19968336/header-bid-tag1', - 'sizes': [ - { - 'w': 728, - 'h': 90 - }, - { - 'w': 970, - 'h': 90 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '543221', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '68136e1c47023d', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220995, - 'status': 1, - 'bid_id': '378a8914450b334' - } - ] - }, - { - 'code': '/19968336/header-bid-tag-0', - 'sizes': [ - { - 'w': 300, - 'h': 250 - }, - { - 'w': 300, - 'h': 600 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '5324321' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '7e5d6af25ed188', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220996, - 'bid_id': '387d9d9c32ca47c' - } - ] - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '392b5a6b05d648', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1, - 'transactionId': 'fsafsa' - }, - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '4dccdc37746135', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897463, - 'status': 1, - 'transactionId': 'fsafsa' - } - ], - 'start': 1462918897460 - }]; - adapterManager.callBids( adUnits, bidRequests, @@ -749,133 +629,483 @@ describe('adapterManager tests', function () { }); }); // end s2s tests - describe('s2sTesting', function () { - let doneStub = sinon.stub(); - let ajaxStub = sinon.stub(); - - function getTestAdUnits() { - // copy adUnits - // return JSON.parse(JSON.stringify(getAdUnits())); - return utils.deepClone(getAdUnits()).map(adUnit => { - adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'rubicon'], bid.bidder)); - return adUnit; - }) - } - - function callBids(adUnits = getTestAdUnits()) { - let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); - adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); - } - - 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.deepClone(CONFIG); - Object.assign(TESTING_CONFIG, { - bidders: ['appnexus', 'adequant'], - testing: true - }); - let stubGetSourceBidderMap; - + describe('Multiple S2S tests', function () { beforeEach(function () { - config.setConfig({s2sConfig: TESTING_CONFIG}); + config.setConfig({s2sConfig: [CONFIG, CONFIG2]}); adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; - adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; - adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; - - stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); - prebidServerAdapterMock.callBids.reset(); - adequantAdapterMock.callBids.reset(); - appnexusAdapterMock.callBids.reset(); - rubiconAdapterMock.callBids.reset(); }); afterEach(function () { - config.setConfig({s2sConfig: {}}); - s2sTesting.getSourceBidderMap.restore(); - }); - - it('calls server adapter if no sources defined', function () { - 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', function () { - 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', function () { - 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', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - callBids(); - - // server adapter - checkServerCalled(2, 2); - - // appnexus - checkClientCalled(appnexusAdapterMock, 2); - - // adequant - checkClientCalled(adequantAdapterMock, 2); + allS2SBidders.length = 0; }); - it('does not call server adapter for bidders that go to client', function () { - 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 + const bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }, + { + 'bidderCode': 'pubmatic', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '2342342342lfi23', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '5324321' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': '4r42r23r23' + }, + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': '4r42r23r23' + } + ], + 'start': 1462918897460 + }]; + + it('invokes callBids on the S2S adapter', function () { + adapterManager.callBids( + getAdUnits(), + bidRequests, + () => {}, + () => () => {} + ); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + // Enable this test when prebidServer adapter is made 1.0 compliant + it('invokes callBids with only s2s bids', function () { + const adUnits = getAdUnits(); + // adUnit without appnexus bidder + adUnits.push({ + 'code': '123', + 'sizes': [300, 250], + 'bids': [ + { + 'bidder': 'adequant', + 'params': { + 'publisher_id': '1234567', + 'bidfloor': 0.01 + } + } + ] + }); + + adapterManager.callBids( + adUnits, + bidRequests, + () => {}, + () => () => {} + ); + const requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; + expect(requestObj.ad_units.length).to.equal(2); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + describe('BID_REQUESTED event', function () { + // function to count BID_REQUESTED events + let cnt, count = () => cnt++; + + beforeEach(function () { + prebidServerAdapterMock.callBids.reset(); + cnt = 0; + events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); + + afterEach(function () { + events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); + + it('should fire for s2s requests', function () { + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(2); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + it('should fire for simultaneous s2s and client requests', function () { + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(3); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + sinon.assert.calledOnce(adequantAdapterMock.callBids); + adequantAdapterMock.callBids.reset(); + delete adapterManager.bidderRegistry['adequant']; + }); + }); + }); // end multiple s2s tests + + describe('s2sTesting', function () { + let doneStub = sinon.stub(); + let ajaxStub = sinon.stub(); + + function getTestAdUnits() { + // copy adUnits + // return JSON.parse(JSON.stringify(getAdUnits())); + return utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'rubicon'], bid.bidder)); + return adUnit; + }) + } + + function callBids(adUnits = getTestAdUnits()) { + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); + } + + 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.deepClone(CONFIG); + Object.assign(TESTING_CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true + }); + let stubGetSourceBidderMap; + + beforeEach(function () { + config.setConfig({s2sConfig: TESTING_CONFIG}); + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + + stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); + + prebidServerAdapterMock.callBids.reset(); + adequantAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.reset(); + rubiconAdapterMock.callBids.reset(); + }); + + afterEach(function () { + s2sTesting.getSourceBidderMap.restore(); + }); + + it('calls server adapter if no sources defined', function () { + 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', function () { + 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', function () { + 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', function () { + 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 @@ -885,61 +1115,389 @@ describe('adapterManager tests', function () { checkClientCalled(adequantAdapterMock, 2); }); - it('does not call client adapters for bidders that go to server', function () { - 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); + it('does not call client adapters for bidders that go to server', function () { + 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', function () { + 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', function () { + 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('Multiple Server s2sTesting', function () { + let doneStub = sinon.stub(); + let ajaxStub = sinon.stub(); + + function getTestAdUnits() { + // copy adUnits + return utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + return includes(['adequant', 'appnexus', 'pubmatic', 'rubicon'], + bid.bidder); + }); + return adUnit; + }) + } + + function callBids(adUnits = getTestAdUnits()) { + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); + } + + function checkServerCalled(numAdUnits, firstConfigNumBids, secondConfigNumBids) { + let requestObjects = []; + let configBids; + if (firstConfigNumBids === 0 || secondConfigNumBids === 0) { + configBids = Math.max(firstConfigNumBids, secondConfigNumBids) + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + let requestObj1 = prebidServerAdapterMock.callBids.firstCall.args[0]; + requestObjects.push(requestObj1) + } else { + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + let requestObj1 = prebidServerAdapterMock.callBids.firstCall.args[0]; + let requestObj2 = prebidServerAdapterMock.callBids.secondCall.args[0]; + requestObjects.push(requestObj1, requestObj2); + } + + requestObjects.forEach((requestObj, index) => { + const numBids = configBids !== undefined ? configBids : index === 0 ? firstConfigNumBids : secondConfigNumBids + 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' || bid.bidder === 'pubmatic'; + }).length).to.equal(numBids); + } + }) + } + + function checkClientCalled(adapter, numBids) { + sinon.assert.calledOnce(adapter.callBids); + expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids); + } + + beforeEach(function () { + allS2SBidders.length = 0; + clientTestAdapters.length = 0 + + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + adapterManager.bidderRegistry['pubmatic'] = pubmaticAdapterMock; + + prebidServerAdapterMock.callBids.reset(); + adequantAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.reset(); + rubiconAdapterMock.callBids.reset(); + pubmaticAdapterMock.callBids.reset(); + }); + + it('calls server adapter if no sources defined for config where testing is true, ' + + 'calls client adapter for second config where testing is false', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true, + }); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + + callBids(); + + // server adapter + checkServerCalled(2, 2, 1); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + sinon.assert.called(rubiconAdapterMock.callBids); + }); + + it('calls client adapter if one client source defined for config where testing is true, ' + + 'calls client adapter for second config where testing is false', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); + + // server adapter + checkServerCalled(2, 1, 1); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); + }); + + it('calls client adapters if client sources defined in first config and server in second config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + + callBids(); + + // server adapter + checkServerCalled(2, 0, 1); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); + }); + + it('does not call server adapter for bidders that go to client when both configs are set to client', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); - // server adapter - checkServerCalled(2, 2); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + bidderControl: { + pubmatic: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); + + sinon.assert.notCalled(prebidServerAdapterMock.callBids); // appnexus - sinon.assert.notCalled(appnexusAdapterMock.callBids); + checkClientCalled(appnexusAdapterMock, 2); // adequant - sinon.assert.notCalled(adequantAdapterMock.callBids); + checkClientCalled(adequantAdapterMock, 2); + + // pubmatic + checkClientCalled(pubmaticAdapterMock, 2); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); }); - it('calls client and server adapters for bidders that go to both', function () { - 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); + it('does not call client adapters for bidders in either config when testServerOnly if true in first config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + testServerOnly: true, + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + bidderControl: { + pubmatic: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + } + }, + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); // server adapter - checkServerCalled(2, 2); + checkServerCalled(2, 1, 0); // appnexus - checkClientCalled(appnexusAdapterMock, 2); + sinon.assert.notCalled(appnexusAdapterMock.callBids); // adequant - checkClientCalled(adequantAdapterMock, 2); + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + sinon.assert.notCalled(rubiconAdapterMock.callBids); }); - it('makes mixed client/server adapter calls for mixed bidder sources', function () { - 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); + it('does not call client adapters for bidders in either config when testServerOnly if true in second config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testServerOnly: true, + bidderControl: { + pubmatic: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + } + }, + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); // server adapter - checkServerCalled(1, 2); + checkServerCalled(2, 1, 1); // appnexus - checkClientCalled(appnexusAdapterMock, 1); + sinon.assert.notCalled(appnexusAdapterMock.callBids); // adequant - checkClientCalled(adequantAdapterMock, 1); + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + sinon.assert.notCalled(rubiconAdapterMock.callBids); }); }); @@ -988,6 +1546,27 @@ describe('adapterManager tests', function () { expect(adapterManager.aliasRegistry).to.have.property('s2sAlias'); }); + it('should allow an alias if alias is part of s2sConfig.bidders for multiple s2sConfigs', function () { + let testS2sConfig = utils.deepClone(CONFIG); + testS2sConfig.bidders = ['s2sAlias']; + config.setConfig({s2sConfig: [ + testS2sConfig, { + enabled: true, + endpoint: 'rp-pbs-endpoint-test.com', + timeout: 500, + maxBids: 1, + adapter: 'prebidServer', + bidders: ['s2sRpAlias'], + accountId: 'def' + } + ]}); + + adapterManager.aliasBidAdapter('s2sBidder', 's2sAlias'); + expect(adapterManager.aliasRegistry).to.have.property('s2sAlias'); + adapterManager.aliasBidAdapter('s2sBidder', 's2sRpAlias'); + expect(adapterManager.aliasRegistry).to.have.property('s2sRpAlias'); + }); + it('should throw an error if alias + bidder are unknown and not part of s2sConfig.bidders', function () { let testS2sConfig = utils.deepClone(CONFIG); testS2sConfig.bidders = ['s2sAlias']; @@ -1003,6 +1582,7 @@ describe('adapterManager tests', function () { describe('makeBidRequests', function () { let adUnits; beforeEach(function () { + allS2SBidders.length = 0 adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; @@ -1054,6 +1634,8 @@ describe('adapterManager tests', function () { describe('sizeMapping', function () { beforeEach(function () { + allS2SBidders.length = 0; + clientTestAdapters.length = 0; sinon.stub(window, 'matchMedia').callsFake(() => ({matches: true})); }); @@ -1187,7 +1769,7 @@ describe('adapterManager tests', function () { [] ); - // only valid sizes as specified in size config should show up in bidRequests + // only valid sizes as specified in size config should show up in bidRequests bidRequests.forEach(bidRequest => { bidRequest.bids.forEach(bid => { bid.sizes.forEach(size => { @@ -1294,9 +1876,13 @@ describe('adapterManager tests', function () { describe('s2sTesting - testServerOnly', () => { beforeEach(() => { config.setConfig({ s2sConfig: getServerTestingConfig(CONFIG) }); + allS2SBidders.length = 0 + s2sTesting.bidSource = {}; }); - afterEach(() => config.resetConfig()); + afterEach(() => { + config.resetConfig(); + }); const makeBidRequests = ads => { let bidRequests = adapterManager.makeBidRequests( @@ -1413,5 +1999,192 @@ describe('adapterManager tests', function () { } ); }); + + describe('Multiple s2sTesting - testServerOnly', () => { + beforeEach(() => { + config.setConfig({s2sConfig: [getServerTestingConfig(CONFIG), CONFIG2]}); + }); + + afterEach(() => { + config.resetConfig() + allS2SBidders.length = 0; + s2sTesting.bidSource = {}; + }); + + const makeBidRequests = ads => { + let bidRequests = adapterManager.makeBidRequests( + ads, 1111, 2222, 1000 + ); + + bidRequests.sort((a, b) => { + if (a.bidderCode < b.bidderCode) return -1; + if (a.bidderCode > b.bidderCode) return 1; + return 0; + }); + + return bidRequests; + }; + + const removeAdUnitsBidSource = adUnits => adUnits.map(adUnit => { + const newAdUnit = { ...adUnit }; + newAdUnit.bids = newAdUnit.bids.map(bid => { + if (bid.bidSource) delete bid.bidSource; + return bid; + }); + return newAdUnit; + }); + + it('suppresses all client bids if there are server bids resulting from bidSource at the adUnit Level', () => { + let ads = getServerTestingsAds(); + ads.push({ + code: 'test_div_5', + sizes: [[300, 250]], + bids: [{ bidder: 'pubmatic' }] + }) + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('openx'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('pubmatic'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('rubicon'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + }); + + it('should not surpress client side bids if testServerOnly is true in one config, ' + + ',bidderControl resolves to server in another config' + + 'and there are no bid with bidSource at the adUnit Level', () => { + let testConfig1 = utils.deepClone(getServerTestingConfig(CONFIG)); + let testConfig2 = utils.deepClone(CONFIG2); + testConfig1.testServerOnly = false; + testConfig2.testServerOnly = true; + testConfig2.testing = true; + testConfig2.bidderControl = { + 'pubmatic': { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }; + config.setConfig({s2sConfig: [testConfig1, testConfig2]}); + + let ads = [ + { + code: 'test_div_1', + sizes: [[300, 250]], + bids: [{ bidder: 'adequant' }] + }, + { + code: 'test_div_2', + sizes: [[300, 250]], + bids: [{ bidder: 'openx' }] + }, + { + code: 'test_div_3', + sizes: [[300, 250]], + bids: [{ bidder: 'pubmatic' }] + }, + ]; + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('adequant'); + expect(bidRequests[0].bids[0].finalSource).equals('client'); + + expect(bidRequests[1].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('openx'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('pubmatic'); + expect(bidRequests[2].bids[0].finalSource).equals('client'); + }); + + // todo: update description + it('suppresses all, and only, client bids if there are bids resulting from bidSource at the adUnit Level', () => { + const ads = getServerTestingsAds(); + + // change this adUnit to be server based + ads[1].bids[1].bidSource.client = 0; + ads[1].bids[1].bidSource.server = 100; + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[1].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('openx'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('rubicon'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + }); + + // we have a server call now + it('does not suppress client bids if no "test case" bids result in a server bid', () => { + const ads = getServerTestingsAds(); + + // change this adUnit to be client based + ads[0].bids[0].bidSource.client = 100; + ads[0].bids[0].bidSource.server = 0; + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(4); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('adequant'); + expect(bidRequests[0].bids[0].finalSource).equals('client'); + + expect(bidRequests[1].bids).lengthOf(2); + expect(bidRequests[1].bids[0].bidder).equals('appnexus'); + expect(bidRequests[1].bids[0].finalSource).equals('client'); + expect(bidRequests[1].bids[1].bidder).equals('appnexus'); + expect(bidRequests[1].bids[1].finalSource).equals('client'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('openx'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + + expect(bidRequests[3].bids).lengthOf(2); + expect(bidRequests[3].bids[0].bidder).equals('rubicon'); + expect(bidRequests[3].bids[0].finalSource).equals('client'); + expect(bidRequests[3].bids[1].bidder).equals('rubicon'); + expect(bidRequests[3].bids[1].finalSource).equals('client'); + }); + + it( + 'should surpress client side bids if no ad unit bidSources are set, ' + + 'but bidderControl resolves to server', + () => { + const ads = removeAdUnitsBidSource(getServerTestingsAds()); + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(2); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('openx'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[1].bids).lengthOf(2); + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + } + ); + }); }); });