diff --git a/CHANGELOG b/CHANGELOG index 159f542be62..cf89da26f0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +AOL Prebid 1.31.0 +---------------- +Updated to Prebid 0.31.0 + + AOL Prebid 1.30.0 ---------------- Updated to Prebid 0.30.1 diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index e670c6f5b93..77c875b9787 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -264,7 +264,7 @@ } }, { - bidder: 'huddledmasses', + bidder: 'colossusssp', params: { placement_id: 0 } @@ -384,7 +384,7 @@ } }, { - bidder: 'huddledmasses', + bidder: 'colossusssp', params: { placement_id: 0 } diff --git a/modules/appnexusAstBidAdapter.js b/modules/appnexusAstBidAdapter.js index b0997992a0c..c4e2686db15 100644 --- a/modules/appnexusAstBidAdapter.js +++ b/modules/appnexusAstBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bidRequests) { + buildRequests: function(bidRequests, bidderRequest) { const tags = bidRequests.map(bidToTag); const userObjBid = bidRequests.find(hasUserInfo); let userObj; @@ -76,6 +76,7 @@ export const spec = { method: 'POST', url: URL, data: payloadString, + bidderRequest }; }, @@ -85,18 +86,27 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse) { + interpretResponse: function(serverResponse, {bidderRequest}) { const bids = []; - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && SUPPORTED_AD_TYPES.includes(rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && SUPPORTED_AD_TYPES.includes(rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } } - } - }); + }); + } return bids; }, @@ -201,6 +211,7 @@ function newBid(serverBid, rtbBid) { image: nativeAd.main_img && nativeAd.main_img.url, icon: nativeAd.icon && nativeAd.icon.url, clickUrl: nativeAd.link.url, + clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, }; } else { diff --git a/modules/appnexusAstBidAdapter.md b/modules/appnexusAstBidAdapter.md new file mode 100644 index 00000000000..2b370e11616 --- /dev/null +++ b/modules/appnexusAstBidAdapter.md @@ -0,0 +1,103 @@ +# Overview + +``` +Module Name: AppnexusAst Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@prebid.org +``` + +# Description + +Connects to Appnexus exchange for bids. + +AppnexusAst bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'appnexusAst', + params: { + placementId: '10433394' + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[300, 250], [300,600]], + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + brand: { + required: true + }, + image: { + required: true + }, + clickUrl: { + required: true + }, + } + }, + bids: [{ + bidder: 'appnexusAst', + params: { + placementId: '9880618' + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream' + }, + }, + bids: [{ + bidder: 'appnexusAst', + params: { + placementId: '9333431', + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [ + { + bidder: 'appnexusAst', + params: { + placementId: '5768085', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + } + ] + } +]; +``` diff --git a/modules/huddledmassesBidAdapter.js b/modules/colossussspBidAdapter.js similarity index 85% rename from modules/huddledmassesBidAdapter.js rename to modules/colossussspBidAdapter.js index 76511ee129d..8cf6239c8e5 100644 --- a/modules/huddledmassesBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -6,7 +6,7 @@ import {ajax} from 'src/ajax'; import {STATUS} from 'src/constants'; import adaptermanager from 'src/adaptermanager'; -var BIDDER_CODE = 'huddledmasses'; +var BIDDER_CODE = 'colossusssp'; var sizeObj = { 1: '468x60', @@ -39,7 +39,7 @@ var sizeObj = { utils._each(sizeObj, (item, key) => sizeObj[item] = key); -function HuddledMassesAdapter() { +function ColossusSspAdapter() { function _callBids(bidderRequest) { var bids = bidderRequest.bids || []; @@ -50,9 +50,9 @@ function HuddledMassesAdapter() { handleRpCB(responseText, bid); } catch (err) { if (typeof err === 'string') { - utils.logWarn(`${err} when processing huddledmasses response for placement code ${bid.placementCode}`); + utils.logWarn(`${err} when processing colossus response for placement code ${bid.placementCode}`); } else { - utils.logError('Error processing huddledmasses response for placement code ' + bid.placementCode, null, err); + utils.logError('Error processing colossus response for placement code ' + bid.placementCode, null, err); } var badBid = bidfactory.createBid(STATUS.NO_BID, bid); badBid.bidderCode = bid.bidder; @@ -64,7 +64,7 @@ function HuddledMassesAdapter() { try { ajax(buildOptimizedCall(bid), bidCallback, undefined, { withCredentials: true }); } catch (err) { - utils.logError('Error sending huddledmasses request for placement code ' + bid.placementCode, null, err); + utils.logError('Error sending colossus request for placement code ' + bid.placementCode, null, err); } }); } @@ -72,7 +72,7 @@ function HuddledMassesAdapter() { function buildOptimizedCall(bid) { bid.startTime = (new Date()).getTime(); - var parsedSizes = HuddledMassesAdapter.masSizeOrdering( + var parsedSizes = ColossusSspAdapter.masSizeOrdering( Array.isArray(bid.params.sizes) ? bid.params.sizes.map(size => (sizeObj[size] || '').split('x')) : bid.sizes ); @@ -117,7 +117,7 @@ function HuddledMassesAdapter() { index % 2 === 0 && queryString[index + 1] !== undefined ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, - '//huddledmassessupply.com/?' + '//colossusssp.com/?' ).slice(0, -1); } @@ -136,12 +136,12 @@ function HuddledMassesAdapter() { bidmanager.addBidResponse(bidRequest.placementCode, bid); } - return Object.assign(new Adapter(BIDDER_CODE), { // BIDDER_CODE huddledmasses + return Object.assign(new Adapter(BIDDER_CODE), { // BIDDER_CODE colossusssp callBids: _callBids }); } -HuddledMassesAdapter.masSizeOrdering = function (sizes) { +ColossusSspAdapter.masSizeOrdering = function (sizes) { var MAS_SIZE_PRIORITY = [15, 2, 9]; return utils.parseSizesInput(sizes) .reduce((result, size) => { @@ -169,6 +169,6 @@ HuddledMassesAdapter.masSizeOrdering = function (sizes) { }); }; -adaptermanager.registerBidAdapter(new HuddledMassesAdapter(), 'huddledmasses'); +adaptermanager.registerBidAdapter(new ColossusSspAdapter(), BIDDER_CODE); -module.exports = HuddledMassesAdapter; +module.exports = ColossusSspAdapter; diff --git a/modules/express.js b/modules/express.js index 5d3a91c6e8e..8a5dc095476 100644 --- a/modules/express.js +++ b/modules/express.js @@ -20,6 +20,8 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { utils.logWarn('no valid adUnits found, not loading ' + MODULE_NAME); } + // store gpt slots in a more performant hash lookup by elementId (adUnit code) + var gptSlotCache = {}; // put adUnits in a more performant hash lookup by code. var adUnitsCache = adUnits.reduce(function (cache, adUnit) { if (adUnit.code && adUnit.bids) { @@ -72,7 +74,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { const adUnit = adUnitsCache[elemId]; if (adUnit) { - adUnit._gptSlot = gptSlot; + gptSlotCache[elemId] = gptSlot; // store by elementId adUnit.sizes = adUnit.sizes || mapGptSlotSizes(gptSlot.getSizes()); adUnits.push(adUnit); gptSlots.splice(i, 1); @@ -141,7 +143,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { - return adUnit._gptSlot; + return gptSlotCache[adUnit.code]; }) ]); } @@ -157,7 +159,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { // get already displayed adUnits from aGptSlots if provided, else all defined gptSlots aGptSlots = defaultSlots(aGptSlots); var adUnits = pickAdUnits(/* mutated: */ aGptSlots).filter(function (adUnit) { - return adUnit._gptSlot._displayed; + return gptSlotCache[adUnit.code]._displayed; }); if (aGptSlots.length) { @@ -171,7 +173,7 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { - return adUnit._gptSlot + return gptSlotCache[adUnit.code]; }), options ]); diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index dcebc14dfa8..0906a1a0b3d 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -105,7 +105,16 @@ function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(bidRequest) { const isDebug = !!getConfig('debug'); - convertTypes(bidRequest.ad_units); + const adUnits = utils.cloneJson(bidRequest.ad_units); + adUnits.forEach(adUnit => { + let videoMediaType = utils.deepAccess(adUnit, 'mediaTypes.video'); + if (videoMediaType) { + // pbs expects a ad_unit.video attribute if the imp is video + adUnit.video = Object.assign({}, videoMediaType); + delete adUnit.mediaTypes.video; + } + }) + convertTypes(adUnits); let requestJson = { account_id: config.accountId, tid: bidRequest.tid, @@ -114,7 +123,7 @@ function PrebidServer() { secure: config.secure, url: utils.getTopWindowUrl(), prebid_version: '$prebid.version$', - ad_units: bidRequest.ad_units.filter(hasSizes), + ad_units: adUnits.filter(hasSizes), is_debug: isDebug }; diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index 84f955317c0..00b5c014e98 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -53,6 +53,11 @@ export const spec = { type: 'iframe', url: '//bh.contextweb.com/visitormatch' }]; + } else if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: '//bh.contextweb.com/visitormatch/prebid' + }]; } } diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 526e0e129c2..69981ba2b56 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -226,12 +226,11 @@ export const spec = { }, /** * @param {*} responseObj - * @param {bidRequest} bidRequest + * @param {BidRequest} bidRequest * @return {Bid[]} An array of bids which */ interpretResponse: function(responseObj, {bidRequest}) { let ads = responseObj.ads; - const adResponseKey = bidRequest.placementCode; // check overall response if (typeof responseObj !== 'object' || responseObj.status !== 'ok') { @@ -239,8 +238,8 @@ export const spec = { } // video ads array is wrapped in an object - if (bidRequest.mediaType === 'video' && typeof ads === 'object') { - ads = ads[adResponseKey]; + if (typeof bidRequest === 'object' && bidRequest.mediaType === 'video' && typeof ads === 'object') { + ads = ads[bidRequest.placementCode]; } // check the ad response @@ -251,9 +250,9 @@ export const spec = { // if there are multiple ads, sort by CPM ads = ads.sort(_adCpmSort); - let bids = ads.reduce((bids, ad) => { + return ads.reduce((bids, ad) => { if (ad.status !== 'ok') { - return; + return []; } let bid = { @@ -286,8 +285,6 @@ export const spec = { return bids; }, []); - - return bids; }, getUserSyncs: function() { if (!hasSynced) { diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js new file mode 100644 index 00000000000..a821383dc2d --- /dev/null +++ b/modules/s2sTesting.js @@ -0,0 +1,126 @@ +import { config } from 'src/config'; +import { setS2STestingModule } from 'src/adaptermanager'; + +var CONSTANTS = require('src/constants.json'); +const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING; +export const SERVER = 'server'; +export const CLIENT = 'client'; + +var testing = false; // whether testing is turned on +var bidSource = {}; // store bidder sources determined from s2sConfing bidderControl + +// load s2sConfig +config.getConfig('s2sConfig', config => { + testing = config.s2sConfig && config.s2sConfig.testing; + addBidderSourceTargeting(config.s2sConfig) + calculateBidSources(config.s2sConfig); +}); + +// function to add hb_source_ adServerTargeting (AST) kvp to bidder settings +function addBidderSourceTargeting(s2sConfig = {}) { + // bail if testing is not turned on + if (!testing) { + return; + } + var bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; + var bidderControl = s2sConfig.bidderControl || {}; + // for each configured bidder + (s2sConfig.bidders || []).forEach((bidder) => { + // remove any existing kvp setting + if (bidderSettings[bidder] && bidderSettings[bidder][AST]) { + bidderSettings[bidder][AST] = bidderSettings[bidder][AST].filter((kvp) => { + return kvp.key !== `hb_source_${bidder}`; + }); + } + // if includeSourceKvp === true add new kvp setting + if (bidderControl[bidder] && bidderControl[bidder].includeSourceKvp) { + bidderSettings[bidder] = bidderSettings[bidder] || {}; + bidderSettings[bidder][AST] = bidderSettings[bidder][AST] || []; + bidderSettings[bidder][AST].push({ + key: `hb_source_${bidder}`, + val: function (bidResponse) { + // default to client (currently only S2S sets this) + return bidResponse.source || CLIENT; + } + }); + // make sure "alwaysUseBid" is true so targeting is set + bidderSettings[bidder].alwaysUseBid = true; + } + }); +} + +export function getSourceBidderMap(adUnits = []) { + 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) => { + // calculate the source once and store on bid object + bid.calcSource = bid.calcSource || 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 + // 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; + }); + + // return map of source => array of bidders + return { + [SERVER]: Object.keys(sourceBidders[SERVER]), + [CLIENT]: Object.keys(sourceBidders[CLIENT]) + }; +} + +/** + * @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 + */ +function calculateBidSources(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] = getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server + }); +} + +/** + * @function getSource() gets a random source based on the given sourceWeights (export just for testing) + * @param sourceWeights mapping of relative weights of potential sources. for example {server: 1, client: 3} should do a server request 25% of the time and a client request 75% of the time. + * @param bidSources list of possible bid sources: "server", "client". In theory could get the sources from the sourceWeights keys, but this is publisher config defined, so bidSources let's us constrain that. + * @return the chosen source ("server" or "client"), or undefined if none chosen + */ +export function getSource(sourceWeights = {}, bidSources = [SERVER, CLIENT]) { + var srcIncWeight = {}; // store incremental weights of each source + var totWeight = 0; + bidSources.forEach((source) => { + totWeight += (sourceWeights[source] || 0); + srcIncWeight[source] = totWeight; + }); + if (!totWeight) return; // bail if no source weights + // choose a source randomly based on weights + var rndWeight = Math.random() * totWeight; + for (var i = 0; i < bidSources.length; i++) { + let source = bidSources[i]; + // choose the first source with an incremental weight > random weight + if (rndWeight < srcIncWeight[source]) return source; + } +} + +// inject the s2sTesting module into the adaptermanager rather than importing it +// importing it causes the packager to include it even when it's not explicitly included in the build +setS2STestingModule(exports); diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index e8ed973fe5f..2e2831a028a 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -11,6 +11,20 @@ function Spotx() { let bidReq; let KVP_Object; + const _defaultBidderSettings = { + alwaysUseBid: true, + adserverTargeting: [ + { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.spotx_ad_key; + } + } + ] + }; + + bidmanager.registerDefaultBidderSetting('spotx', _defaultBidderSettings); + baseAdapter.callBids = function(bidRequest) { if (!bidRequest || !bidRequest.bids || bidRequest.bids.length === 0) { return; @@ -85,7 +99,7 @@ function Spotx() { bid.cpm = KVP_Object.spotx_bid; bid.vastUrl = url; - bid.ad = url; + bid.spotx_ad_key = KVP_Object.spotx_ad_key; var sizes = utils.isArray(bidReq.sizes[0]) ? bidReq.sizes[0] : bidReq.sizes; bid.height = sizes[1]; diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 44c00c2c0b3..13f893a841d 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -37,7 +37,7 @@ var TrustxAdapter = function TrustxAdapter() { query.push('u=' + encodeURIComponent(location.href)); query.push('auids=' + encodeURIComponent(auids.join(','))); query.push('cb=' + _makeHandler(auids, placementMap)); - query.push('pt=' + (window.globalPrebidTrustxPriceType === 'net' ? 'net' : 'gross')); + query.push('pt=' + (window.globalPrebidTrustxPriceType === 'gross' ? 'gross' : 'net')); adloader.loadScript(reqHost + path + query.join('&')); } diff --git a/package.json b/package.json index 0bc2e858a19..a5b573bcb41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.30.1", + "version": "0.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 48c320548a0..2204e997084 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -8,6 +8,7 @@ import { newBidder } from './adapters/bidderFactory'; var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); var events = require('./events'); +let s2sTestingModule; // store s2sTesting module if it's loaded var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; @@ -100,25 +101,34 @@ exports.callBids = ({adUnits, cbTimeout}) => { s2sAdapter.queueSync({bidderCodes}); } + let clientTestAdapters = []; + let s2sTesting = false; if (_s2sConfig.enabled) { + // if s2sConfig.bidderControl testing is turned on + s2sTesting = _s2sConfig.testing && typeof s2sTestingModule !== 'undefined'; + if (s2sTesting) { + // get all adapters doing client testing + clientTestAdapters = s2sTestingModule.getSourceBidderMap(adUnits)[s2sTestingModule.CLIENT]; + } + // these are called on the s2s adapter let adaptersServerSide = _s2sConfig.bidders; - // don't call these client side + // don't call these client side (unless client request is needed for testing) bidderCodes = bidderCodes.filter((elm) => { - return !adaptersServerSide.includes(elm); + return !adaptersServerSide.includes(elm) || clientTestAdapters.includes(elm); }); - let adUnitsCopy = utils.cloneJson(adUnits); + let adUnitsS2SCopy = utils.cloneJson(adUnits); // filter out client side bids - adUnitsCopy.forEach((adUnit) => { + adUnitsS2SCopy.forEach((adUnit) => { if (adUnit.sizeMapping) { adUnit.sizes = mapSizes(adUnit); delete adUnit.sizeMapping; } adUnit.sizes = transformHeightWidth(adUnit); adUnit.bids = adUnit.bids.filter((bid) => { - return adaptersServerSide.includes(bid.bidder); + return adaptersServerSide.includes(bid.bidder) && (!s2sTesting || bid.finalSource !== s2sTestingModule.CLIENT); }).map((bid) => { bid.bid_id = utils.getUniqueIdentifierStr(); return bid; @@ -126,7 +136,7 @@ exports.callBids = ({adUnits, cbTimeout}) => { }); // don't send empty requests - adUnitsCopy = adUnitsCopy.filter(adUnit => { + adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnit => { return adUnit.bids.length !== 0; }); @@ -138,7 +148,7 @@ exports.callBids = ({adUnits, cbTimeout}) => { requestId, bidderRequestId, tid, - bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsCopy}), + bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsS2SCopy}), start: new Date().getTime(), auctionStart: auctionStart, timeout: _s2sConfig.timeout, @@ -149,13 +159,28 @@ exports.callBids = ({adUnits, cbTimeout}) => { } }); - let s2sBidRequest = {tid, 'ad_units': adUnitsCopy}; + let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.join(',')}`); if (s2sBidRequest.ad_units.length) { s2sAdapter.callBids(s2sBidRequest); } } + let _bidderRequests = []; + // client side adapters + let adUnitsClientCopy = utils.cloneJson(adUnits); + // filter out s2s bids + adUnitsClientCopy.forEach((adUnit) => { + adUnit.bids = adUnit.bids.filter((bid) => { + return !s2sTesting || bid.finalSource !== s2sTestingModule.SERVER; + }) + }); + + // don't send empty requests + adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => { + return adUnit.bids.length !== 0; + }); + bidderCodes.forEach(bidderCode => { const adapter = _bidderRegistry[bidderCode]; if (adapter) { @@ -164,21 +189,30 @@ exports.callBids = ({adUnits, cbTimeout}) => { bidderCode, requestId, bidderRequestId, - bids: getBids({bidderCode, requestId, bidderRequestId, adUnits}), - start: new Date().getTime(), + bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsClientCopy}), auctionStart: auctionStart, timeout: cbTimeout }; if (bidderRequest.bids && bidderRequest.bids.length !== 0) { - utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); - adapter.callBids(bidderRequest); + _bidderRequests.push(bidderRequest); } - } else { - utils.logError(`Adapter trying to be called which does not exist: ${bidderCode} adaptermanager.callBids`); } }); + + _bidderRequests.forEach(bidRequest => { + bidRequest.start = new Date().getTime(); + const adapter = _bidderRegistry[bidRequest.bidderCode]; + if (adapter) { + if (bidRequest.bids && bidRequest.bids.length !== 0) { + utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + adapter.callBids(bidRequest); + } + } else { + utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`); + } + }) }; function transformHeightWidth(adUnit) { @@ -296,3 +330,9 @@ exports.setBidderSequence = function (order) { exports.setS2SConfig = function (config) { _s2sConfig = config; }; + +// the s2sTesting module is injected when it's loaded rather than being imported +// importing it causes the packager to include it even when it's not explicitly included in the build +exports.setS2STestingModule = function (module) { + s2sTestingModule = module; +}; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 25a25c2d9c9..06592736247 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -66,6 +66,7 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {('GET'|'POST')} method The type of request which this is. * @property {string} url The endpoint for the request. For example, "//bids.example.com". * @property {string|object} data Data to be sent in the request. + * @property {object} options Content-Type set in the header of the bid request, overrides default 'text/plain'. * If this is a GET request, they'll become query params. If it's a POST request, they'll be added to the body. * Strings will be added as-is. Objects will be unpacked into query params based on key/value mappings, or * JSON-serialized into the Request body. @@ -241,10 +242,10 @@ export function newBidder(spec) { error: onFailure }, undefined, - { + Object.assign({ method: 'GET', withCredentials: true - } + }, request.options) ); break; case 'POST': @@ -255,12 +256,12 @@ export function newBidder(spec) { error: onFailure }, typeof request.data === 'string' ? request.data : JSON.stringify(request.data), - { + Object.assign({ method: 'POST', contentType: request.contentType || 'text/plain', customHeaders: request.customHeaders || {}, withCredentials: true - } + }, request.options) ); break; default: diff --git a/src/bidmanager.js b/src/bidmanager.js index 30fc7135bd8..002a1150005 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -1,6 +1,6 @@ import { uniques, flatten, adUnitsFilter, getBidderRequest } from './utils'; import { getPriceBucketString } from './cpmBucketManager'; -import { NATIVE_KEYS, nativeBidIsValid } from './native'; +import { nativeBidIsValid, getNativeTargeting } from './native'; import { isValidVideoBid } from './video'; import { getCacheUrl, store } from './videoCache'; import { Renderer } from 'src/Renderer'; @@ -280,13 +280,8 @@ function getKeyValueTargetingPairs(bidderCode, custBidObj) { custBidObj.sendStandardTargeting = defaultBidderSettingsMap[bidderCode].sendStandardTargeting; } - // set native key value targeting if (custBidObj['native']) { - Object.keys(custBidObj['native']).forEach(asset => { - const key = NATIVE_KEYS[asset]; - const value = custBidObj['native'][asset]; - if (key) { keyValues[key] = value; } - }); + keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } return keyValues; @@ -480,10 +475,16 @@ events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { function adjustBids(bid) { var code = bid.bidderCode; var bidPriceAdjusted = bid.cpm; - if (code && $$PREBID_GLOBAL$$.bidderSettings && $$PREBID_GLOBAL$$.bidderSettings[code]) { - if (typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') { + let bidCpmAdjustment; + if ($$PREBID_GLOBAL$$.bidderSettings) { + if (code && $$PREBID_GLOBAL$$.bidderSettings[code] && typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') { + bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment; + } else if ($$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] && typeof $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment === 'function') { + bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment; + } + if (bidCpmAdjustment) { try { - bidPriceAdjusted = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment.call(null, bid.cpm, Object.assign({}, bid)); + bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); } catch (e) { utils.logError('Error during bid adjustment', 'bidmanager.js', e); } @@ -503,48 +504,49 @@ function getStandardBidderSettings() { let granularity = config.getConfig('priceGranularity'); let bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { - bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = { - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { - return bidResponse.pbAg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { - return bidResponse.pbDg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { - return bidResponse.pbLg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { - return bidResponse.pbMg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { - return bidResponse.pbHg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { - return bidResponse.pbCg; - } - } - }, { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - }, { - key: 'hb_deal', - val: function (bidResponse) { - return bidResponse.dealId; + bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; + } + if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + return bidResponse.pbCg; } } - ] - }; + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + }, { + key: 'hb_deal', + val: function (bidResponse) { + return bidResponse.dealId; + } + } + ]; } return bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; } diff --git a/src/config.js b/src/config.js index 704cb630982..41ba9d25301 100644 --- a/src/config.js +++ b/src/config.js @@ -23,12 +23,12 @@ const DEFAULT_USERSYNC = { }; const GRANULARITY_OPTIONS = { - 'LOW': 'low', - 'MEDIUM': 'medium', - 'HIGH': 'high', - 'AUTO': 'auto', - 'DENSE': 'dense', - 'CUSTOM': 'custom' + LOW: 'low', + MEDIUM: 'medium', + HIGH: 'high', + AUTO: 'auto', + DENSE: 'dense', + CUSTOM: 'custom' }; const ALL_TOPICS = '*'; @@ -174,6 +174,7 @@ export function newConfig() { function setConfig(options) { if (typeof options !== 'object') { utils.logError('setConfig options must be an object'); + return; } Object.assign(config, options); diff --git a/src/constants.json b/src/constants.json index a4649e09c31..806b3790c12 100644 --- a/src/constants.json +++ b/src/constants.json @@ -32,7 +32,8 @@ "BID_RESPONSE": "bidResponse", "BID_WON": "bidWon", "SET_TARGETING": "setTargeting", - "REQUEST_BIDS": "requestBids" + "REQUEST_BIDS": "requestBids", + "ADD_AD_UNITS": "addAdUnits" }, "EVENT_ID_PATHS": { "bidWon": "adUnitCode" diff --git a/src/native.js b/src/native.js index 544258818ec..c992cf9ad61 100644 --- a/src/native.js +++ b/src/native.js @@ -78,6 +78,11 @@ export function nativeBidIsValid(bid) { return false; } + // all native bid responses must define a landing page url + if (!deepAccess(bid, 'native.clickUrl')) { + return false; + } + const requestedAssets = bidRequest.nativeParams; if (!requestedAssets) { return true; @@ -94,14 +99,58 @@ export function nativeBidIsValid(bid) { } /* - * Native responses may have impression trackers. This retrieves the - * impression tracker urls for the given ad object and fires them. + * Native responses may have associated impression or click trackers. + * This retrieves the appropriate tracker urls for the given ad object and + * fires them. As a native creatives may be in a cross-origin frame, it may be + * necessary to invoke this function via postMessage. secureCreatives is + * configured to fire this function when it receives a `message` of 'Prebid Native' + * and an `adId` with the value of the `bid.adId`. When a message is posted with + * these parameters, impression trackers are fired. To fire click trackers, the + * message should contain an `action` set to 'click'. + * + * // Native creative template example usage + * + * %%PATTERN:hb_native_title%% + * + * + * */ -export function fireNativeImpressions(adObject) { - const impressionTrackers = - adObject['native'] && adObject['native'].impressionTrackers; +export function fireNativeTrackers(message, adObject) { + let trackers; + + if (message.action === 'click') { + trackers = adObject['native'] && adObject['native'].clickTrackers; + } else { + trackers = adObject['native'] && adObject['native'].impressionTrackers; + } - (impressionTrackers || []).forEach(tracker => { - triggerPixel(tracker); + (trackers || []).forEach(triggerPixel); +} + +/** + * Gets native targeting key-value paris + * @param {Object} bid + * @return {Object} targeting + */ +export function getNativeTargeting(bid) { + let keyValues = {}; + + Object.keys(bid['native']).forEach(asset => { + const key = NATIVE_KEYS[asset]; + const value = bid['native'][asset]; + if (key) { + keyValues[key] = value; + } }); + + return keyValues; } diff --git a/src/prebid.js b/src/prebid.js index 8ed20ec8b15..d46f2c64227 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -28,6 +28,7 @@ const { syncUsers, triggerUserSyncs } = userSync; var BID_WON = CONSTANTS.EVENTS.BID_WON; var SET_TARGETING = CONSTANTS.EVENTS.SET_TARGETING; +var ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; var auctionRunning = false; var bidRequestQueue = []; @@ -462,6 +463,8 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { adUnitArr.transactionId = utils.generateUUID(); $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); } + // emit event + events.emit(ADD_AD_UNITS); }; /** @@ -623,8 +626,17 @@ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { }; /** - * Will enable sending a prebid.js to data provider specified - * @param {Object} config object {provider : 'string', options : {}} + * Enable sending analytics data to the analytics provider of your + * choice. + * + * For usage, see [Integrate with the Prebid Analytics + * API](http://prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). + * + * For a list of supported analytics adapters, see [Analytics for + * Prebid](http://prebid.org/overview/analytics.html). + * @param {Object} config + * @param {string} config.provider The name of the provider, e.g., `"ga"` for Google Analytics. + * @param {Object} config.options The options for this particular analytics adapter. This will likely vary between adapters. */ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { if (config && !utils.isEmpty(config)) { diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 2402ba755f4..efc1386fde3 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,7 +4,7 @@ */ import events from './events'; -import { fireNativeImpressions } from './native'; +import { fireNativeTrackers } from './native'; import { EVENTS } from './constants'; const BID_WON = EVENTS.BID_WON; @@ -42,7 +42,7 @@ function receiveMessage(ev) { // adId: '%%PATTERN:hb_adid%%' // }), '*'); if (data.message === 'Prebid Native') { - fireNativeImpressions(adObject); + fireNativeTrackers(data, adObject); $$PREBID_GLOBAL$$._winningBids.push(adObject); events.emit(BID_WON, adObject); } diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index 5263741885a..967258a7fbc 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -198,7 +198,7 @@ describe('bidmanager.js', function () { assert.deepEqual(response, expected); }); - it('Custom bidCpmAdjustment for one bidder and inherit standard', function () { + it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () { $$PREBID_GLOBAL$$.bidderSettings = { appnexus: { @@ -207,6 +207,9 @@ describe('bidmanager.js', function () { }, }, standard: { + bidCpmAdjustment: function (bidCpm) { + return 200; + }, adserverTargeting: [ { key: 'hb_bidder', @@ -235,6 +238,27 @@ describe('bidmanager.js', function () { assert.deepEqual(response, expected); }); + it('Standard bidCpmAdjustment changes the bid of any bidder', function () { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[5] + ); + + assert.equal(bid.cpm, 0.5); + + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.5; + } + } + }; + + bidmanager.adjustBids(bid) + assert.equal(bid.cpm, 0.25); + }); + it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -590,7 +614,10 @@ describe('bidmanager.js', function () { { bidderCode: 'appnexusAst', mediaType: 'native', - native: {title: 'foo'} + native: { + title: 'foo', + clickUrl: 'example.link' + } } ); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index efdfd911728..14452987091 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -1,4 +1,4 @@ -import { excpet } from 'chai'; +import { expect } from 'chai'; import { assert } from 'chai'; import { newConfig } from 'src/config'; @@ -33,6 +33,11 @@ describe('config API', () => { expect(getConfig('baz')).to.equal('qux'); }); + it('only accepts objects', () => { + setConfig('invalid'); + expect(getConfig('0')).to.not.equal('i'); + }); + it('sets multiple config properties', () => { setConfig({ foo: 'bar' }); setConfig({ biz: 'buz' }); diff --git a/test/spec/modules/appnexusAstBidAdapter_spec.js b/test/spec/modules/appnexusAstBidAdapter_spec.js index f8288819a55..d07ee6df543 100644 --- a/test/spec/modules/appnexusAstBidAdapter_spec.js +++ b/test/spec/modules/appnexusAstBidAdapter_spec.js @@ -304,8 +304,9 @@ describe('AppNexusAdapter', () => { 'mediaType': 'banner' } ]; + let bidderRequest; - let result = spec.interpretResponse(response); + let result = spec.interpretResponse(response, {bidderRequest}); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); }); @@ -319,8 +320,9 @@ describe('AppNexusAdapter', () => { 'nobid': true }] }; + let bidderRequest; - let result = spec.interpretResponse(response); + let result = spec.interpretResponse(response, {bidderRequest}); expect(result.length).to.equal(0); }); @@ -339,8 +341,9 @@ describe('AppNexusAdapter', () => { }] }] }; + let bidderRequest; - let result = spec.interpretResponse(response); + let result = spec.interpretResponse(response, {bidderRequest}); expect(result[0]).to.have.property('vastUrl'); expect(result[0]).to.have.property('descriptionUrl'); expect(result[0]).to.have.property('mediaType', 'video'); @@ -371,8 +374,9 @@ describe('AppNexusAdapter', () => { }, 'impression_trackers': ['http://example.com'], }; + let bidderRequest; - let result = spec.interpretResponse(response1); + let result = spec.interpretResponse(response1, {bidderRequest}); expect(result[0].native.title).to.equal('Native Creative'); expect(result[0].native.body).to.equal('Cool description great stuff'); expect(result[0].native.cta).to.equal('Do it'); diff --git a/test/spec/modules/huddledmassesBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js similarity index 88% rename from test/spec/modules/huddledmassesBidAdapter_spec.js rename to test/spec/modules/colossussspBidAdapter_spec.js index f4cc12dde1b..e8435d90679 100644 --- a/test/spec/modules/huddledmassesBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -1,16 +1,16 @@ import { expect } from 'chai'; -import Adapter from '../../../modules/huddledmassesBidAdapter'; +import Adapter from '../../../modules/colossussspBidAdapter'; import adapterManager from 'src/adaptermanager'; import bidManager from 'src/bidmanager'; import CONSTANTS from 'src/constants.json'; -describe('HuddledMasses adapter tests', function () { +describe('ColossusSSP adapter tests', function () { let sandbox; const adUnit = { - code: 'huddledmasses', + code: 'colossusssp', sizes: [[300, 250], [300, 600]], bids: [{ - bidder: 'huddledmasses', + bidder: 'colossusssp', params: { placement_id: 0 } @@ -34,7 +34,7 @@ describe('HuddledMasses adapter tests', function () { sandbox.restore(); }); - describe('HuddledMasses callBids validation', () => { + describe('ColossusSSP callBids validation', () => { let bids, server; @@ -50,7 +50,7 @@ describe('HuddledMasses adapter tests', function () { server.restore(); }); - let adapter = adapterManager.bidderRegistry['huddledmasses']; + let adapter = adapterManager.bidderRegistry['colossusssp']; it('Valid bid-request', () => { sandbox.stub(adapter, 'callBids'); @@ -65,7 +65,7 @@ describe('HuddledMasses adapter tests', function () { .with.lengthOf(1); expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'huddledmasses'); + .to.have.property('bidder', 'colossusssp'); expect(bidderRequest).to.have.deep.property('bids[0]') .with.property('sizes') @@ -88,7 +88,7 @@ describe('HuddledMasses adapter tests', function () { expect(bids).to.be.lengthOf(1); expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[0].bidderCode).to.equal('huddledmasses'); + expect(bids[0].bidderCode).to.equal('colossusssp'); expect(bids[0].width).to.equal(300); expect(bids[0].height).to.equal(250); expect(bids[0].cpm).to.equal(0.712); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index c781406da6e..5b0a9d37d57 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -387,7 +387,7 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript with correct parameters', () => { sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); }); }); @@ -402,7 +402,7 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript with correct parameters', () => { sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pubid%22%3A1032%2C%22pkey%22%3A%22data_team_test_hb_smoke_test%22%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pubid%22%3A1032%2C%22pkey%22%3A%22data_team_test_hb_smoke_test%22%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); }); }); @@ -417,7 +417,7 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript with correct parameters', () => { sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); }); }); @@ -432,7 +432,7 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript with correct parameters', () => { sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); }); }); @@ -447,8 +447,8 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript twice with correct parameters', () => { sinon.assert.calledTwice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); }); }); @@ -463,9 +463,9 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript thrice with correct parameters', () => { sinon.assert.calledThrice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543212%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%227g8h9i%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543212%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%227g8h9i%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); }); }); @@ -493,7 +493,7 @@ describe('improvedigital adapter tests', function () { }); it('should call loadScript twice with correct parameters', () => { sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22pbjs.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%2C%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); + sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%2C%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); }); }); diff --git a/test/spec/modules/orbitsoftBidAdapter_spec.js b/test/spec/modules/orbitsoftBidAdapter_spec.js index dc37be73483..4b24787f56b 100644 --- a/test/spec/modules/orbitsoftBidAdapter_spec.js +++ b/test/spec/modules/orbitsoftBidAdapter_spec.js @@ -14,7 +14,7 @@ describe('Orbitsoft Adapter tests', function () { describe('test orbitsoft callback response', function () { it('should exist and be a function', function () { - expect(pbjs.handleOASCB).to.exist.and.to.be.a('function'); + expect($$PREBID_GLOBAL$$.handleOASCB).to.exist.and.to.be.a('function'); }); it('should add empty bid responses if no bids returned', function () { @@ -43,8 +43,8 @@ describe('Orbitsoft Adapter tests', function () { cpm: 0 }; - pbjs._bidsRequested.push(bidderRequest); - pbjs.handleOASCB(response); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$.handleOASCB(response); let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; @@ -79,8 +79,8 @@ describe('Orbitsoft Adapter tests', function () { cpm: 0 }; - pbjs._bidsRequested.push(bidderRequest); - pbjs.handleOASCB(response); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$.handleOASCB(response); expect(stubAddBidResponse.getCall(0)).to.equal(null); stubAddBidResponse.restore(); @@ -116,8 +116,8 @@ describe('Orbitsoft Adapter tests', function () { height: 250 }; - pbjs._bidsRequested.push(bidderRequest); - pbjs.handleOASCB(response); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$.handleOASCB(response); let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; @@ -277,9 +277,9 @@ describe('Orbitsoft Adapter tests', function () { height: 250 }; - pbjs._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - pbjs.handleOASCB(response); + $$PREBID_GLOBAL$$.handleOASCB(response); let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; let adUrl = bidResponse1.adUrl; @@ -339,8 +339,8 @@ describe('Orbitsoft Adapter tests', function () { height: 250 }; - pbjs._bidsRequested.push(bidderRequest); - pbjs.handleOASCB(response); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$.handleOASCB(response); let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; let adUrl = bidResponse1.adUrl; diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index 8e1f12dac93..96f5c7a8d1f 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -224,6 +224,14 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch'); }); + it('Verifies image pixel sync', () => { + const options = spec.getUserSyncs({ pixelEnabled: true}); + expect(options).to.not.be.undefined; + expect(options).to.have.lengthOf(1); + expect(options[0].type).to.equal('image'); + expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch/prebid'); + }); + it('Verify app requests', () => { const request = spec.buildRequests(appSlotConfig); const ortbRequest = JSON.parse(request.data); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 5573de34f27..620fc56e516 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,6 +1,5 @@ import { expect } from 'chai'; import adapterManager from 'src/adaptermanager'; -import bidManager from 'src/bidmanager'; import { spec, masSizeOrdering, resetUserSync } from 'modules/rubiconBidAdapter'; import { parse as parseQuery } from 'querystring'; import { newBidder } from 'src/adapters/bidderFactory'; @@ -12,7 +11,6 @@ const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be s describe('the rubicon adapter', () => { let sandbox, - adUnit, bidderRequest; function createVideoBidderRequest() { @@ -56,36 +54,6 @@ describe('the rubicon adapter', () => { beforeEach(() => { sandbox = sinon.sandbox.create(); - sandbox.useFakeServer(); - - adUnit = { - code: '/19968336/header-bid-tag-0', - sizes: [[300, 250], [320, 50]], - mediaType: 'video', - bids: [ - { - bidder: 'rubicon', - params: { - accountId: '14062', - siteId: '70608', - zoneId: '335918', - userId: '12346', - keywords: ['a', 'b', 'c'], - inventory: { - rating: '5-star', - prodtype: 'tech' - }, - visitor: { - ucat: 'new', - lastsearch: 'iphone' - }, - position: 'atf', - referrer: 'localhost' - } - } - ] - }; - bidderRequest = { bidderCode: 'rubicon', requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', @@ -128,43 +96,6 @@ describe('the rubicon adapter', () => { sandbox.restore(); }); - describe('callBids public interface', () => { - let rubiconAdapter = adapterManager.bidderRegistry['rubicon']; - - it('should receive a well-formed bidRequest from the adaptermanager', () => { - sandbox.stub(rubiconAdapter, 'callBids'); - - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - - let bidderRequest = rubiconAdapter.callBids.getCall(0).args[0]; - - expect(bidderRequest).to.have.property('bids') - .that.is.an('array') - .with.lengthOf(1); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'rubicon'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('mediaType', 'video'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('placementCode', adUnit.code); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('sizes') - .that.is.an('array') - .with.lengthOf(2) - .that.deep.equals(adUnit.sizes); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .that.deep.equals(adUnit.bids[0].params); - }); - }); - describe('MAS mapping / ordering', () => { it('should not include values without a proper mapping', () => { // two invalid sizes included: [42, 42], [1, 1] @@ -191,39 +122,16 @@ describe('the rubicon adapter', () => { }); }); - describe('callBids implementation', () => { - let rubiconAdapter; - - let bids, - addBidResponseAction; - - beforeEach(() => { - rubiconAdapter = newBidder(spec); - - bids = []; - - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - if (typeof addBidResponseAction === 'function') { - addBidResponseAction(); - addBidResponseAction = undefined; - } - }); - }); - + describe('buildRequests implementation', () => { describe('for requests', () => { describe('to fastlane', () => { - it('should make a well-formed request', () => { - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; + it('should make a well-formed request objects', () => { + sandbox.stub(Math, 'random', () => 0.1); - let [path, query] = request.url.split('?'); - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); - expect(path).to.equal( - '//fastlane.rubiconproject.com/a/api/fastlane.json' - ); + expect(request.url).to.equal('//fastlane.rubiconproject.com/a/api/fastlane.json'); let expectedQuery = { 'account_id': '14062', @@ -234,6 +142,7 @@ describe('the rubicon adapter', () => { 'p_pos': 'atf', 'rp_floor': '0.01', 'rp_secure': /[01]/, + 'rand': '0.1', 'tk_flint': INTEGRATION, 'tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, @@ -250,61 +159,50 @@ describe('the rubicon adapter', () => { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(query[key]).to.match(value); + expect(data[key]).to.match(value); } else { - expect(query[key]).to.equal(value); + expect(data[key]).to.equal(value); } }); - - expect(query).to.have.property('rand'); }); it('should use rubicon sizes if present', () => { var sizesBidderRequest = clone(bidderRequest); sizesBidderRequest.bids[0].params.sizes = [55, 57, 59]; - rubiconAdapter.callBids(sizesBidderRequest); - - let query = parseQuery(sandbox.server.requests[0].url.split('?')[1]); + let [request] = spec.buildRequests(sizesBidderRequest.bids, sizesBidderRequest); + let data = parseQuery(request.data); - expect(query['size_id']).to.equal('55'); - expect(query['alt_size_ids']).to.equal('57,59'); + expect(data['size_id']).to.equal('55'); + expect(data['alt_size_ids']).to.equal('57,59'); }); - it('should not send a request and register an error bid if no valid sizes', () => { + it('should not validate bid request if no valid sizes', () => { var sizesBidderRequest = clone(bidderRequest); sizesBidderRequest.bids[0].sizes = [[620, 250], [300, 251]]; - rubiconAdapter.callBids(sizesBidderRequest); + let result = spec.isBidRequestValid(sizesBidderRequest.bids[0]); - expect(sandbox.server.requests.length).to.equal(0); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(result).to.equal(false); }); - it('should not send a request and register an error if no account id is present', () => { + it('should not validate bid request if no account id is present', () => { var noAccountBidderRequest = clone(bidderRequest); delete noAccountBidderRequest.bids[0].params.accountId; - rubiconAdapter.callBids(noAccountBidderRequest); + let result = spec.isBidRequestValid(noAccountBidderRequest.bids[0]); - expect(sandbox.server.requests.length).to.equal(0); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(result).to.equal(false); }); it('should allow a floor override', () => { var floorBidderRequest = clone(bidderRequest); floorBidderRequest.bids[0].params.floor = 2; - rubiconAdapter.callBids(floorBidderRequest); + let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); + let data = parseQuery(request.data); - let query = parseQuery(sandbox.server.requests[0].url.split('?')[1]); - - expect(query['rp_floor']).to.equal('2'); + expect(data['rp_floor']).to.equal('2'); }); it('should send digitrust params', () => { @@ -322,12 +220,8 @@ describe('the rubicon adapter', () => { }) ); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let expectedQuery = { 'dt.id': 'testId', @@ -338,25 +232,21 @@ describe('the rubicon adapter', () => { // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(query[key]).to.equal(value); + expect(data[key]).to.equal(value); }); delete window.DigiTrust; }); it('should not send digitrust params when DigiTrust not loaded', () => { - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); }); @@ -375,18 +265,14 @@ describe('the rubicon adapter', () => { }) ); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); delete window.DigiTrust; @@ -407,18 +293,14 @@ describe('the rubicon adapter', () => { }) ); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); delete window.DigiTrust; @@ -453,12 +335,8 @@ describe('the rubicon adapter', () => { return config[key]; }); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let expectedQuery = { 'dt.id': 'testId', @@ -468,7 +346,7 @@ describe('the rubicon adapter', () => { // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(query[key]).to.equal(value); + expect(data[key]).to.equal(value); }); // should not have called DigiTrust.getUser() @@ -490,18 +368,14 @@ describe('the rubicon adapter', () => { return config[key]; }); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); // should not have called DigiTrust.getUser() @@ -523,18 +397,14 @@ describe('the rubicon adapter', () => { return config[key]; }); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); // should not have called DigiTrust.getUser() @@ -547,18 +417,14 @@ describe('the rubicon adapter', () => { return config[key]; }); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; - - let query = request.url.split('?')[1]; - query = parseQuery(query); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); let undefinedKeys = ['dt.id', 'dt.keyv']; // Test that none of the DigiTrust keys are part of the query undefinedKeys.forEach(key => { - expect(typeof query[key]).to.equal('undefined'); + expect(typeof data[key]).to.equal('undefined'); }); // should have called DigiTrust.getUser() once @@ -575,12 +441,10 @@ describe('the rubicon adapter', () => { bidderRequest.auctionStart + 100 ); - rubiconAdapter.callBids(bidderRequest); - - let request = sandbox.server.requests[0]; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; let url = request.url; - let post = JSON.parse(request.requestBody); expect(url).to.equal('//fastlane-adv.rubiconproject.com/v1/auction/video'); @@ -642,17 +506,15 @@ describe('the rubicon adapter', () => { // enter an explicit floor price // floorBidderRequest.bids[0].params.floor = 3.25; - rubiconAdapter.callBids(floorBidderRequest); - - let request = sandbox.server.requests[0]; - let post = JSON.parse(request.requestBody); + let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); + let post = request.data; let floor = post.slots[0].floor; expect(floor).to.equal(3.25); }); - it('should trap when no video object is passed in', () => { + it('should not validate bid request when no video object is passed in', () => { createVideoBidderRequestNoVideo(); sandbox.stub(Date, 'now', () => bidderRequest.auctionStart + 100 @@ -660,9 +522,9 @@ describe('the rubicon adapter', () => { var floorBidderRequest = clone(bidderRequest); - rubiconAdapter.callBids(floorBidderRequest); + let result = spec.isBidRequestValid(floorBidderRequest.bids[0]); - expect(sandbox.server.requests.length).to.equal(0); + expect(result).to.equal(false); }); it('should get size from bid.sizes too', () => { @@ -673,10 +535,8 @@ describe('the rubicon adapter', () => { var floorBidderRequest = clone(bidderRequest); - rubiconAdapter.callBids(floorBidderRequest); - - let request = sandbox.server.requests[0]; - let post = JSON.parse(request.requestBody); + let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); + let post = request.data; expect(post.slots[0].width).to.equal(300); expect(post.slots[0].height).to.equal(250); @@ -684,10 +544,10 @@ describe('the rubicon adapter', () => { }); }); - describe('response handler', () => { + describe('interpretResponse', () => { describe('for fastlane', () => { it('should handle a success response and sort by cpm', () => { - sandbox.server.respondWith(JSON.stringify({ + let response = { 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -742,34 +602,32 @@ describe('the rubicon adapter', () => { ] } ] - })); - - rubiconAdapter.callBids(bidderRequest); - - sandbox.server.respond(); + }; - expect(bidManager.addBidResponse.calledTwice).to.equal(true); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); expect(bids).to.be.lengthOf(2); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].width).to.equal(320); expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); expect(bids[0].creative_id).to.equal('crid-9'); + expect(bids[0].currency).to.equal('USD'); expect(bids[0].ad).to.contain(`alert('foo')`) .and.to.contain(``) .and.to.contain(`
`); expect(bids[0].rubiconTargeting.rpfl_elemid).to.equal('/19968336/header-bid-tag-0'); expect(bids[0].rubiconTargeting.rpfl_14062).to.equal('43_tier_all_test'); - expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); expect(bids[1].bidderCode).to.equal('rubicon'); expect(bids[1].width).to.equal(300); expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); expect(bids[1].creative_id).to.equal('crid-9'); + expect(bids[1].currency).to.equal('USD'); expect(bids[1].ad).to.contain(`alert('foo')`) .and.to.contain(``) .and.to.contain(`
`); @@ -778,7 +636,7 @@ describe('the rubicon adapter', () => { }); it('should be fine with a CPM of 0', () => { - sandbox.server.respondWith(JSON.stringify({ + let response = { 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -794,48 +652,18 @@ describe('the rubicon adapter', () => { 'cpm': 0, 'size_id': 15 }] - })); - - rubiconAdapter.callBids(bidderRequest); - - sandbox.server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - }); - - it('should return currency "USD"', () => { - sandbox.server.respondWith(JSON.stringify({ - 'status': 'ok', - 'account_id': 14062, - 'site_id': 70608, - 'zone_id': 530022, - 'size_id': 15, - 'alt_size_ids': [ - 43 - ], - 'tracking': '', - 'inventory': {}, - 'ads': [{ - 'status': 'ok', - 'cpm': 0, - 'size_id': 15 - }] - })); - - rubiconAdapter.callBids(bidderRequest); + }; - sandbox.server.respond(); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[0].currency).to.equal('USD'); + expect(bids[0].cpm).to.be.equal(0); }); it('should handle an error with no ads returned', () => { - sandbox.server.respondWith(JSON.stringify({ + let response = { 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -847,19 +675,17 @@ describe('the rubicon adapter', () => { 'tracking': '', 'inventory': {}, 'ads': [] - })); - - rubiconAdapter.callBids(bidderRequest); + }; - sandbox.server.respond(); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bids).to.be.lengthOf(0); }); - it('should handle an error with bad status', () => { - sandbox.server.respondWith(JSON.stringify({ + it('should handle an error', () => { + let response = { 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -873,71 +699,23 @@ describe('the rubicon adapter', () => { 'ads': [{ 'status': 'not_ok', }] - })); - - rubiconAdapter.callBids(bidderRequest); + }; - sandbox.server.respond(); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bids).to.be.lengthOf(0); }); it('should handle an error because of malformed json response', () => { - sandbox.server.respondWith('{test{'); - - rubiconAdapter.callBids(bidderRequest); - - sandbox.server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); - - it('should handle error contacting endpoint', () => { - sandbox.server.respondWith([404, {}, '']); - - rubiconAdapter.callBids(bidderRequest); + let response = '{test{'; - sandbox.server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); - - it('should not register an error bid when a success call to addBidResponse throws an error', () => { - sandbox.server.respondWith(JSON.stringify({ - 'status': 'ok', - 'account_id': 14062, - 'site_id': 70608, - 'zone_id': 530022, - 'size_id': 15, - 'alt_size_ids': [ - 43 - ], - 'tracking': '', - 'inventory': {}, - 'ads': [{ - 'status': 'ok', - 'cpm': 0.8, - 'size_id': 15 - }] - })); - - addBidResponseAction = function() { - throw new Error('test error'); - }; - - rubiconAdapter.callBids(bidderRequest); - - sandbox.server.respond(); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); - // was calling twice for same bid, but should only call once - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); + expect(bids).to.be.lengthOf(0); }); }); @@ -947,7 +725,7 @@ describe('the rubicon adapter', () => { }); it('should register a successful bid', () => { - sandbox.server.respondWith(JSON.stringify({ + let response = { 'status': 'ok', 'ads': { '/19968336/header-bid-tag-0': [ @@ -972,18 +750,14 @@ describe('the rubicon adapter', () => { ] }, 'account_id': 7780 - })); - - rubiconAdapter.callBids(bidderRequest); - - sandbox.server.respond(); + }; - // was calling twice for same bid, but should only call once - expect(bidManager.addBidResponse.calledOnce).to.equal(true); + let bids = spec.interpretResponse(response, { + bidRequest: bidderRequest.bids[0] + }); expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].creative_id).to.equal('crid-999999'); expect(bids[0].cpm).to.equal(1); @@ -998,78 +772,25 @@ describe('the rubicon adapter', () => { }); describe('user sync', () => { - let rubiconAdapter; - let userSyncStub; const emilyUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; beforeEach(() => { - // monitor userSync registrations - userSyncStub = sandbox.stub(userSync, 'registerSync'); - sandbox.stub(bidManager, 'addBidResponse'); - - sandbox.server.respondWith(JSON.stringify({ - 'status': 'ok', - 'account_id': 14062, - 'site_id': 70608, - 'zone_id': 530022, - 'size_id': 15, - 'alt_size_ids': [ - 43 - ], - 'tracking': '', - 'inventory': {}, - 'ads': [ - { - 'status': 'ok', - 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', - 'size_id': '15', - 'ad_id': '6', - 'advertiser': 7, - 'network': 8, - 'creative_id': 9, - 'type': 'script', - 'script': 'alert(\'foo\')', - 'campaign_id': 10, - 'cpm': 0.811, - 'targeting': [ - { - 'key': 'rpfl_14062', - 'values': [ - '15_tier_all_test' - ] - } - ] - } - ] - })); - - // Remove all Emily iframes for a fresh start - let iframes = document.querySelectorAll('[src="' + emilyUrl + '"]'); - for (let i = 0; i < iframes.length; i += 1) { - iframes[i].outerHTML = ''; - } - - rubiconAdapter = newBidder(spec); resetUserSync(); }); it('should register the Emily iframe', () => { - expect(userSyncStub.calledOnce).to.be.false; - rubiconAdapter.callBids(bidderRequest); - sandbox.server.respond(); - expect(userSyncStub.calledOnce).to.be.true; - expect(userSyncStub.getCall(0).args).to.eql(['iframe', 'rubicon', emilyUrl]); + let syncs = spec.getUserSyncs(); + + expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); }); it('should not register the Emily iframe more than once', () => { - expect(userSyncStub.calledOnce).to.be.false; - rubiconAdapter.callBids(bidderRequest); - sandbox.server.respond(); - expect(userSyncStub.calledOnce).to.be.true; - // run another auction, should still have only been called once - rubiconAdapter.callBids(bidderRequest); - sandbox.server.respond(); - expect(userSyncStub.calledOnce).to.be.true; + let syncs = spec.getUserSyncs(); + expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); + + // when called again, should still have only been called once + syncs = spec.getUserSyncs(); + expect(syncs).to.equal(undefined); }); }); }); diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js new file mode 100644 index 00000000000..f829087a967 --- /dev/null +++ b/test/spec/modules/s2sTesting_spec.js @@ -0,0 +1,437 @@ +import { getSourceBidderMap, calculateBidSources, getSource } from 'modules/s2sTesting'; +import { config } from 'src/config'; + +var events = require('src/events'); +var CONSTANTS = require('src/constants.json'); +const BID_ADJUSTMENT = CONSTANTS.EVENTS.BID_ADJUSTMENT; + +var expect = require('chai').expect; + +describe('s2sTesting', function () { + let mathRandomStub; + let randomNumber = 0; + + beforeEach(() => { + mathRandomStub = sinon.stub(Math, 'random', () => { return randomNumber; }); + }); + + afterEach(() => { + mathRandomStub.restore(); + }); + + describe('getSource', () => { + // helper function to set random number and get the source + function getExpectedSource(randNumber, sourceWeights, sources) { + // set random number for testing + randomNumber = randNumber; + return getSource(sourceWeights, sources); + } + + it('returns undefined if no sources', () => { + expect(getExpectedSource(0, {})).to.be.undefined; + expect(getExpectedSource(0.5, {})).to.be.undefined; + expect(getExpectedSource(0.9999, {})).to.be.undefined; + }); + + it('returns undefined if no weights', () => { + expect(getExpectedSource(0, {server: 0, client: 0})).to.be.undefined; + expect(getExpectedSource(0.5, {client: 0})).to.be.undefined; + }); + + it('gets the expected source from 3 sources', () => { + var sources = ['server', 'client', 'both']; + expect(getExpectedSource(0, {server: 1, client: 1, both: 2}, sources)).to.equal('server'); + expect(getExpectedSource(0.2499999, {server: 1, client: 1, both: 2}, sources)).to.equal('server'); + expect(getExpectedSource(0.25, {server: 1, client: 1, both: 2}, sources)).to.equal('client'); + expect(getExpectedSource(0.49999, {server: 1, client: 1, both: 2}, sources)).to.equal('client'); + expect(getExpectedSource(0.5, {server: 1, client: 1, both: 2}, sources)).to.equal('both'); + expect(getExpectedSource(0.99999, {server: 1, client: 1, both: 2}, sources)).to.equal('both'); + }); + + it('gets the expected source from 2 sources', () => { + expect(getExpectedSource(0, {server: 2, client: 3})).to.equal('server'); + expect(getExpectedSource(0.39999, {server: 2, client: 3})).to.equal('server'); + expect(getExpectedSource(0.4, {server: 2, client: 3})).to.equal('client'); + expect(getExpectedSource(0.9, {server: 2, client: 3})).to.equal('client'); + var sources = ['server', 'client', 'both']; + expect(getExpectedSource(0, {server: 2, client: 3}, sources)).to.equal('server'); + expect(getExpectedSource(0.39999, {server: 2, client: 3}, sources)).to.equal('server'); + expect(getExpectedSource(0.4, {server: 2, client: 3}, sources)).to.equal('client'); + expect(getExpectedSource(0.9, {server: 2, client: 3}, sources)).to.equal('client'); + }); + + it('gets the expected source from 1 source', () => { + expect(getExpectedSource(0, {client: 2})).to.equal('client'); + expect(getExpectedSource(0.5, {client: 2})).to.equal('client'); + expect(getExpectedSource(0.99999, {client: 2})).to.equal('client'); + }); + + it('ignores an invalid source', () => { + expect(getExpectedSource(0, {client: 2, cache: 2})).to.equal('client'); + expect(getExpectedSource(0.3333, {server: 1, cache: 1, client: 2})).to.equal('server'); + expect(getExpectedSource(0.34, {server: 1, cache: 1, client: 2})).to.equal('client'); + }); + + it('ignores order of sources', () => { + var sources = ['server', 'client', 'both']; + expect(getExpectedSource(0, {client: 1, server: 1, both: 2}, sources)).to.equal('server'); + expect(getExpectedSource(0.2499999, {both: 2, client: 1, server: 1}, sources)).to.equal('server'); + expect(getExpectedSource(0.25, {client: 1, both: 2, server: 1}, sources)).to.equal('client'); + expect(getExpectedSource(0.49999, {server: 1, both: 2, client: 1}, sources)).to.equal('client'); + expect(getExpectedSource(0.5, {both: 2, server: 1, client: 1}, sources)).to.equal('both'); + }); + + it('accepts an array of sources', () => { + expect(getExpectedSource(0.3333, {second: 2, first: 1}, ['first', 'second'])).to.equal('first'); + expect(getExpectedSource(0.34, {second: 2, first: 1}, ['first', 'second'])).to.equal('second'); + expect(getExpectedSource(0.9999, {second: 2, first: 1}, ['first', 'second'])).to.equal('second'); + }); + }); + + describe('getSourceBidderMap', () => { + describe('setting source through s2sConfig', () => { + beforeEach(() => { + // set random number for testing + randomNumber = 0.7; + }); + + it('does not work if testing is "false"', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon'], + testing: false, + bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} + }}); + expect(getSourceBidderMap()).to.eql({ + server: [], + client: [] + }); + }); + + it('sets one client bidder', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon'], + testing: true, + bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} + }}); + expect(getSourceBidderMap()).to.eql({ + server: [], + client: ['rubicon'] + }); + }); + + it('sets one server bidder', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon'], + testing: true, + bidderControl: {rubicon: {bidSource: {server: 4, client: 1}}} + }}); + expect(getSourceBidderMap()).to.eql({ + server: ['rubicon'], + client: [] + }); + }); + + it('defaults to server', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon'], + testing: true + }}); + expect(getSourceBidderMap()).to.eql({ + server: ['rubicon'], + client: [] + }); + }); + + it('sets two bidders', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {bidSource: {server: 3, client: 1}}, + appnexus: {bidSource: {server: 1, client: 1}} + }}}); + var serverClientBidders = getSourceBidderMap(); + expect(serverClientBidders.server).to.eql(['rubicon']); + expect(serverClientBidders.client).to.have.members(['appnexus']); + }); + }); + + describe('setting source through adUnits', () => { + beforeEach(() => { + // reset s2sconfig bid sources + config.setConfig({s2sConfig: {testing: true}}); + // set random number for testing + randomNumber = 0.7; + }); + + it('sets one bidder source from one adUnit', () => { + var adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {server: 4, client: 1}} + ]} + ]; + expect(getSourceBidderMap(adUnits)).to.eql({ + server: ['rubicon'], + client: [] + }); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.equal('server'); + expect(adUnits[0].bids[0].finalSource).to.equal('server'); + + adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {server: 1, client: 1}} + ]} + ]; + expect(getSourceBidderMap(adUnits)).to.eql({ + server: [], + client: ['rubicon'] + }); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.equal('client'); + expect(adUnits[0].bids[0].finalSource).to.equal('client'); + }); + + it('defaults to client if no bidSource', () => { + var adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {}} + ]} + ]; + expect(getSourceBidderMap(adUnits)).to.eql({ + server: [], + client: ['rubicon'] + }); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.be.undefined; + expect(adUnits[0].bids[0].finalSource).to.equal('client'); + }); + + it('sets multiple bidders sources from one adUnit', () => { + var adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {server: 2, client: 1}}, + {bidder: 'appnexus', bidSource: {server: 3, client: 1}} + ]} + ]; + var serverClientBidders = getSourceBidderMap(adUnits); + expect(serverClientBidders.server).to.eql(['appnexus']); + expect(serverClientBidders.client).to.have.members(['rubicon']); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.equal('client'); + expect(adUnits[0].bids[0].finalSource).to.equal('client'); + expect(adUnits[0].bids[1].calcSource).to.equal('server'); + expect(adUnits[0].bids[1].finalSource).to.equal('server'); + }); + + it('sets multiple bidders sources from multiple adUnits', () => { + var adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {server: 2, client: 1}}, + {bidder: 'appnexus', bidSource: {server: 1, client: 1}} + ]}, + {bids: [ + {bidder: 'rubicon', bidSource: {server: 4, client: 1}}, + {bidder: 'bidder3', bidSource: {client: 1}} + ]} + ]; + var serverClientBidders = 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']); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.equal('client'); + expect(adUnits[0].bids[0].finalSource).to.equal('client'); + expect(adUnits[0].bids[1].calcSource).to.equal('client'); + expect(adUnits[0].bids[1].finalSource).to.equal('client'); + expect(adUnits[1].bids[0].calcSource).to.equal('server'); + expect(adUnits[1].bids[0].finalSource).to.equal('server'); + expect(adUnits[1].bids[1].calcSource).to.equal('client'); + expect(adUnits[1].bids[1].finalSource).to.equal('client'); + }); + + it('should reuse calculated sources', () => { + var adUnits = [ + {bids: [ + {bidder: 'rubicon', calcSource: 'client', bidSource: {server: 4, client: 1}}, + {bidder: 'appnexus', calcSource: 'server', bidSource: {server: 1, client: 1}}, + {bidder: 'bidder3', calcSource: 'server', bidSource: {client: 1}} + ]} + ]; + var serverClientBidders = getSourceBidderMap(adUnits); + + expect(serverClientBidders.server).to.have.members(['appnexus', 'bidder3']); + expect(serverClientBidders.server).to.not.have.members(['rubicon']); + expect(serverClientBidders.client).to.have.members(['rubicon']); + expect(serverClientBidders.client).to.not.have.members(['appnexus', 'bidder3']); + // should have saved the source on the bid + expect(adUnits[0].bids[0].calcSource).to.equal('client'); + expect(adUnits[0].bids[0].finalSource).to.equal('client'); + expect(adUnits[0].bids[1].calcSource).to.equal('server'); + expect(adUnits[0].bids[1].finalSource).to.equal('server'); + expect(adUnits[0].bids[2].calcSource).to.equal('server'); + expect(adUnits[0].bids[2].finalSource).to.equal('server'); + }); + }); + + describe('setting source through s2sconfig and adUnits', () => { + beforeEach(() => { + // reset s2sconfig bid sources + config.setConfig({s2sConfig: {testing: true}}); + // set random number for testing + randomNumber = 0.7; + }); + + it('should get sources from both', () => { + // set rubicon: server and appnexus: client + var adUnits = [ + {bids: [ + {bidder: 'rubicon', bidSource: {server: 4, client: 1}}, + {bidder: 'appnexus', bidSource: {client: 1}} + ]} + ]; + + // set rubicon: client and appnexus: server + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {bidSource: {server: 2, client: 1}}, + appnexus: {bidSource: {server: 1}} + } + }}); + + var serverClientBidders = getSourceBidderMap(adUnits); + expect(serverClientBidders.server).to.have.members(['rubicon', 'appnexus']); + expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus']); + }); + }); + }); + + describe('addBidderSourceTargeting', () => { + const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING; + + function checkTargeting(bidder) { + var targeting = window.pbjs.bidderSettings[bidder][AST]; + var srcTargeting = targeting[targeting.length - 1]; + expect(srcTargeting.key).to.equal(`hb_source_${bidder}`); + expect(srcTargeting.val).to.be.a('function'); + expect(window.pbjs.bidderSettings[bidder].alwaysUseBid).to.be.true; + } + + function checkNoTargeting(bidder) { + var bs = window.pbjs.bidderSettings; + var targeting = bs[bidder] && bs[bidder][AST]; + if (!targeting) { + expect(targeting).to.be.undefined; + return; + } + expect(targeting.find((kvp) => { + return kvp.key === `hb_source_${bidder}`; + })).to.be.undefined; + } + + function checkTargetingVal(bidResponse, expectedVal) { + var targeting = window.pbjs.bidderSettings[bidResponse.bidderCode][AST]; + var targetingFunc = targeting[targeting.length - 1].val; + expect(targetingFunc(bidResponse)).to.equal(expectedVal); + } + + beforeEach(() => { + // set bidderSettings + window.pbjs.bidderSettings = {}; + }); + + it('should not set hb_source_ unless testing is on and includeSourceKvp is set', () => { + config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus']}}); + expect(window.pbjs.bidderSettings).to.eql({}); + + config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus'], testing: true}}); + expect(window.pbjs.bidderSettings).to.eql({}); + + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {bidSource: {server: 2, client: 1}}, + appnexus: {bidSource: {server: 1}} + } + }}); + expect(window.pbjs.bidderSettings).to.eql({}); + + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: false, + bidderControl: { + rubicon: {includeSourceKvp: true}, + appnexus: {includeSourceKvp: true} + } + }}); + expect(window.pbjs.bidderSettings).to.eql({}); + }); + + it('should set hb_source_ if includeSourceKvp is set', () => { + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {includeSourceKvp: true}, + appnexus: {includeSourceKvp: true} + } + }}); + checkTargeting('rubicon'); + checkTargeting('appnexus'); + checkTargetingVal({bidderCode: 'rubicon', source: 'server'}, 'server'); + checkTargetingVal({bidderCode: 'appnexus', source: 'client'}, 'client'); + + // turn off appnexus + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {includeSourceKvp: true}, + appnexus: {includeSourceKvp: false} + } + }}); + checkTargeting('rubicon'); + checkNoTargeting('appnexus'); + checkTargetingVal({bidderCode: 'rubicon', source: 'client'}, 'client'); + + // should default to "client" + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {includeSourceKvp: true}, + appnexus: {includeSourceKvp: true} + } + }}); + checkTargeting('rubicon'); + checkTargeting('appnexus'); + checkTargetingVal({bidderCode: 'rubicon'}, 'client'); + checkTargetingVal({bidderCode: 'appnexus'}, 'client'); + }); + + it('should reset adServerTargeting when a new config is set', () => { + // set config with targeting + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {includeSourceKvp: true}, + appnexus: {includeSourceKvp: true} + } + }}); + checkTargeting('rubicon'); + checkTargeting('appnexus'); + + // set config without targeting + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true + }}); + checkNoTargeting('rubicon'); + checkNoTargeting('appnexus'); + }); + }); +}); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 94c8f438cc0..b92dfe4d493 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -173,7 +173,7 @@ describe('sharethrough adapter', () => { describe('when bidResponse string cannot be JSON parsed', () => { beforeEach(() => { - pbjs._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); adapter.str.placementCodeSet['foo'] = {}; server.respondWith(/aaaa1111/, 'non JSON string'); @@ -199,7 +199,7 @@ describe('sharethrough adapter', () => { describe('when no fill', () => { beforeEach(() => { - pbjs._bidsRequested.push(bidderRequest); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); adapter.str.placementCodeSet['foo'] = {}; let bidderResponse1 = { diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index e559a16d71a..7208ebef343 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -8,10 +8,10 @@ describe('trustx adapter tests', function () { var bidmanager = require('src/bidmanager'); var adLoader = require('src/adloader'); var utils = require('src/utils'); - window.pbjs = window.pbjs || {}; + window.$$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$ || {}; if (typeof (pbjs) === 'undefined') { - var pbjs = window.pbjs; + var pbjs = window.$$PREBID_GLOBAL$$; } let stubLoadScript; beforeEach(function () { @@ -73,7 +73,7 @@ describe('trustx adapter tests', function () { sinon.assert.calledWith(stubLoadScript, bidUrl); var parsedBidUrl = urlParse(bidUrl); var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - var generatedCallback = 'pbjs.trustx_callback_wrapper_5_6'; + var generatedCallback = '$$PREBID_GLOBAL$$.trustx_callback_wrapper_5_6'; expect(parsedBidUrl.hostname).to.equal('sofia.trustx.org'); expect(parsedBidUrl.pathname).to.equal('/hb'); expect(parsedBidUrlQueryString).to.have.property('auids').and.to.equal('5,6'); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js new file mode 100644 index 00000000000..977575a4d19 --- /dev/null +++ b/test/spec/native_spec.js @@ -0,0 +1,46 @@ +import { expect } from 'chai'; +import { fireNativeTrackers, getNativeTargeting } from 'src/native'; +const utils = require('src/utils'); + +const bid = { + native: { + title: 'Native Creative', + body: 'Cool description great stuff', + cta: 'Do it', + sponsoredBy: 'AppNexus', + clickUrl: 'https://www.link.example', + clickTrackers: ['https://tracker.example'], + impressionTrackers: ['https://impression.example'], + } +}; + +describe('native.js', () => { + let triggerPixelStub; + + beforeEach(() => { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(() => { + utils.triggerPixel.restore(); + }); + + it('gets native targeting keys', () => { + const targeting = getNativeTargeting(bid); + expect(targeting.hb_native_title).to.equal(bid.native.title); + expect(targeting.hb_native_body).to.equal(bid.native.body); + expect(targeting.hb_native_linkurl).to.equal(bid.native.clickUrl); + }); + + it('fires impression trackers', () => { + fireNativeTrackers({}, bid); + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, bid.native.impressionTrackers[0]); + }); + + it('fires click trackers', () => { + fireNativeTrackers({ action: 'click' }, bid); + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); + }); +}); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 10bd6074021..6da22ed8984 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -4,6 +4,7 @@ import { getAdUnits } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +var s2sTesting = require('../../../../modules/s2sTesting'); const CONFIG = { enabled: true, @@ -19,12 +20,21 @@ var prebidServerAdapterMock = { setConfig: sinon.stub(), queueSync: sinon.stub() }; +var adequantAdapterMock = { + bidder: 'adequant', + callBids: sinon.stub(), + setConfig: sinon.stub(), + queueSync: sinon.stub() +}; +var appnexusAdapterMock = { + bidder: 'appnexus', + callBids: sinon.stub(), + setConfig: sinon.stub(), + queueSync: sinon.stub() +}; describe('adapterManager tests', () => { describe('S2S tests', () => { - var stubGetStorageItem; - var stubSetStorageItem; - beforeEach(() => { AdapterManager.setS2SConfig(CONFIG); AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; @@ -83,6 +93,179 @@ describe('adapterManager tests', () => { }); }) + describe('s2sTesting', () => { + function getTestAdUnits() { + // copy adUnits + return JSON.parse(JSON.stringify(getAdUnits())); + } + + function checkServerCalled(numAdUnits, numBids) { + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + var 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); + } + + var TESTING_CONFIG; + var stubGetSourceBidderMap; + + beforeEach(() => { + TESTING_CONFIG = Object.assign(CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true + }); + + AdapterManager.setS2SConfig(CONFIG); + AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + + stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); + + prebidServerAdapterMock.callBids.reset(); + adequantAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.reset(); + }); + + afterEach(() => { + s2sTesting.getSourceBidderMap.restore(); + }); + + it('calls server adapter if no sources defined', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: [], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + AdapterManager.callBids({adUnits: getTestAdUnits()}); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client adapter if one client source defined', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + AdapterManager.callBids({adUnits: getTestAdUnits()}); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client adapters if client sources defined', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + AdapterManager.callBids({adUnits: getTestAdUnits()}); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('does not call server adapter for bidders that go to client', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + 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; + AdapterManager.callBids({adUnits}); + + // server adapter + sinon.assert.notCalled(prebidServerAdapterMock.callBids); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('does not call client adapters for bidders that go to server', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + 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; + AdapterManager.callBids({adUnits}); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client and server adapters for bidders that go to both', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + 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; + AdapterManager.callBids({adUnits}); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('makes mixed client/server adapter calls for mixed bidder sources', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + AdapterManager.setS2SConfig(TESTING_CONFIG); + 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; + AdapterManager.callBids({adUnits}); + + // server adapter + checkServerCalled(1, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 1); + + // adequant + checkClientCalled(adequantAdapterMock, 1); + }); + }); + describe('aliasBidderAdaptor', function() { const CODE = 'sampleBidder'; diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 114123bb711..a32d6cbdfb6 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -173,6 +173,31 @@ describe('bidders created by newBidder', () => { }); }); + it('should make the appropriate POST request when options are passed', () => { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + const options = { contentType: 'application/json'}; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: url, + data: data, + options: options + }); + + bidder.callBids(MOCK_BIDS_REQUEST); + + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(url); + expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(data)); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({ + method: 'POST', + contentType: 'application/json', + withCredentials: true + }); + }); + it('should make the appropriate GET request', () => { const bidder = newBidder(spec); const url = 'test.url.com'; @@ -195,6 +220,30 @@ describe('bidders created by newBidder', () => { }); }); + it('should make the appropriate GET request when options are passed', () => { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + const opt = { withCredentials: false } + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'GET', + url: url, + data: data, + options: opt + }); + + bidder.callBids(MOCK_BIDS_REQUEST); + + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); + expect(ajaxStub.firstCall.args[2]).to.be.undefined; + expect(ajaxStub.firstCall.args[3]).to.deep.equal({ + method: 'GET', + withCredentials: false + }); + }); + it('should make multiple calls if the spec returns them', () => { const bidder = newBidder(spec); const url = 'test.url.com';