diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..3127d1c03a9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.js] +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/CHANGELOG b/CHANGELOG index 6eadf5b1b75..ef979910b96 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +AOL Prebid 1.21.0 +---------------- +Updated to Prebid 0.23.1 + + AOL Prebid 1.20.0 ---------------- Updated to Prebid 0.22.2 diff --git a/adapters.json b/adapters.json index 21c86b0244e..44e70d432b2 100644 --- a/adapters.json +++ b/adapters.json @@ -30,8 +30,10 @@ "openx", "piximedia", "pubmatic", + "pubgears", "pulsepoint", "pulsepointLite", + "quantcast", "rhythmone", "rubicon", "smartyads", @@ -64,6 +66,8 @@ "atomx", "tapsense", "trion", + "prebidServer", + "adsupply", { "appnexus": { "alias": "brealtime" diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 616a1377a95..2380db9fc29 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -250,6 +250,13 @@ zone: '2eb6bd58-865c-47ce-af7f-a918108c3fd2' // REQUIRED zone oid } }, + { + bidder: 'quantcast', + params: { + publisherId: 'test-publisher', // REQUIRED Publisher Id provided by Quantcast + battr : [1,2] // OPTIONAL - Array of Blocked creative attributes as per OpenRTB Spec List 5.3 + } + }, { bidder: 'atomx', params: { @@ -363,6 +370,13 @@ pId: 123456 // REQUIRED Placement Id } }, + { + bidder: 'quantcast', + params: { + publisherId: 'test-publisher', // REQUIRED Publisher Id provided by Quantcast + battr : [1,2] // OPTIONAL - Array of Blocked creative attributes as per OpenRTB Spec List 5.3 + } + }, { bidder: 'huddledmasses', params: { diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html new file mode 100644 index 00000000000..de28f6f293d --- /dev/null +++ b/integrationExamples/gpt/prebidServer_example.html @@ -0,0 +1,95 @@ + + + + + + + + +

Prebid.js S2S Example

+ +
Div-1
+
+ +
+ + diff --git a/package.json b/package.json index ad895b06c00..b6764249d44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.22.2", + "version": "0.23.1", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -82,6 +82,7 @@ "karma-sinon-ie": "^2.0.0-rc10", "karma-webpack": "^1.5.1", "localtunnel": "^1.3.0", + "lodash": "^4.17.4", "mkpath": "^1.0.0", "mocha": "^1.21.4", "mock-fs": "^3.11.0", diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 9bb18cfec12..27c5ddca30b 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -11,6 +11,8 @@ import { BaseAdapter } from './adapters/baseAdapter'; var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; +//create s2s settings objectType_function +let _s2sConfig = {}; var _analyticsRegistry = {}; let _bidderSequence = null; @@ -31,7 +33,7 @@ function getBids({bidderCode, requestId, bidderRequestId, adUnits}) { mediaType: adUnit.mediaType, transactionId : adUnit.transactionId, sizes: sizes, - bidId: utils.getUniqueIdentifierStr(), + bidId: bid.bid_id || utils.getUniqueIdentifierStr(), bidderRequestId, requestId }); @@ -55,6 +57,55 @@ exports.callBids = ({adUnits, cbTimeout}) => { bidderCodes = shuffle(bidderCodes); } + if(_s2sConfig.enabled) { + //these are called on the s2s adapter + let adaptersServerSide = _s2sConfig.bidders; + + //don't call these client side + bidderCodes = bidderCodes.filter((elm) => { + return !adaptersServerSide.includes(elm); + }); + let adUnitsCopy = utils.cloneJson(adUnits); + + //filter out client side bids + adUnitsCopy.forEach((adUnit) => { + if (adUnit.sizeMapping) { + adUnit.sizes = mapSizes(adUnit); + delete adUnit.sizeMapping; + } + adUnit.sizes = transformHeightWidth(adUnit); + adUnit.bids = adUnit.bids.filter((bid) => { + return adaptersServerSide.includes(bid.bidder); + }).map((bid) => { + bid.bid_id = utils.getUniqueIdentifierStr(); + return bid; + }); + }); + + let tid = utils.generateUUID(); + adaptersServerSide.forEach(bidderCode => { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + requestId, + bidderRequestId, + tid, + bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits' : adUnitsCopy}), + start: new Date().getTime(), + auctionStart: auctionStart, + timeout: _s2sConfig.timeout + }; + //Pushing server side bidder + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + }); + + let s2sBidRequest = {tid, 'ad_units' : adUnitsCopy}; + let s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; //jshint ignore:line + utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.join(',')}`); + s2sAdapter.setConfig(_s2sConfig); + s2sAdapter.callBids(s2sBidRequest); + } + bidderCodes.forEach(bidderCode => { const adapter = _bidderRegistry[bidderCode]; if (adapter) { @@ -80,6 +131,21 @@ exports.callBids = ({adUnits, cbTimeout}) => { }); }; + +function transformHeightWidth(adUnit) { + let sizesObj = []; + let sizes = utils.parseSizesInput(adUnit.sizes); + sizes.forEach(size => { + let heightWidth = size.split('x'); + let sizeObj = { + 'w' : parseInt(heightWidth[0]), + 'h' : parseInt(heightWidth[1]) + }; + sizesObj.push(sizeObj); + }); + return sizesObj; +} + exports.registerBidAdapter = function (bidAdaptor, bidderCode) { if (bidAdaptor && bidderCode) { @@ -158,6 +224,10 @@ exports.setBidderSequence = function (order) { _bidderSequence = order; }; +exports.setS2SConfig = function (config) { + _s2sConfig = config; +}; + /** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */ /** END INSERT ADAPTERS */ diff --git a/src/adapters/adsupply.js b/src/adapters/adsupply.js new file mode 100644 index 00000000000..d6c3cda6334 --- /dev/null +++ b/src/adapters/adsupply.js @@ -0,0 +1,86 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); +var utils = require('../utils'); +const ADSUPPLY_CODE = 'adsupply'; + +var AdSupplyAdapter = function AdSupplyAdapter() { + function _validateParams(params) { + if (!params || !params.siteId || !params.zoneId || !params.endpointUrl || !params.clientId) { + return false; + } + + if (typeof params.zoneId !== 'number' || params.zoneId <= 0) { + return false; + } + + return true; + } + + function _getRequestUrl(bid) { + var referrerUrl = encodeURIComponent(window.document.referrer); + var rand = encodeURIComponent(Math.floor(Math.random() * 100000 + 1)); + var time = encodeURIComponent(new Date().getTimezoneOffset()); + return '//' + bid.params.endpointUrl + '/banner.engine?id=' + bid.params.siteId + '&z=' + bid.params.zoneId + '&rand=' + rand + '&ver=async' + '&time=' + time + '&referrerurl=' + referrerUrl + '&abr=false' + '&hbt=1&cid=' + encodeURIComponent(bid.params.clientId); + } + + $$PREBID_GLOBAL$$.adSupplyResponseHandler = function (bidId) { + if (!bidId) return; + + var bidRequest = utils.getBidRequest(bidId); + + if (!bidRequest || !bidRequest.params) return; + + var clientId = bidRequest.params.clientId; + var zoneProp = 'b' + bidRequest.params.zoneId; + + if (!window[clientId] || !window[clientId][zoneProp]) return; + + var media = window[clientId][zoneProp].Media; + + if (!media) return; + + if (!media.Url || !media.Ecpm || typeof media.Ecpm !== 'number' || media.Ecpm <= 0) { + var noFillbject = bidfactory.createBid(2, bidRequest); + noFillbject.bidderCode = ADSUPPLY_CODE; + bidmanager.addBidResponse(bidRequest.placementCode, noFillbject); + } else { + var bidObject = bidfactory.createBid(1, bidRequest); + bidObject.bidderCode = ADSUPPLY_CODE; + bidObject.cpm = media.Ecpm; + bidObject.ad = ''; + bidObject.width = media.Width; + bidObject.height = media.Height; + bidmanager.addBidResponse(bidRequest.placementCode, bidObject); + } + }; + + function _makeCallBackHandler(bidId) { + return function () { + $$PREBID_GLOBAL$$.adSupplyResponseHandler(bidId); + }; + } + + function _callBids(params) { + var bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + if (!_validateParams(bid.params)) continue; + + var clientId = bid.params.clientId; + var zoneProp = 'b' + bid.params.zoneId; + window[clientId] = window[clientId] || {}; + window.window[clientId][zoneProp] = window.window[clientId][zoneProp] || {}; + window.window[clientId][zoneProp].Media = {}; + + var requestUrl = _getRequestUrl(bid); + adloader.loadScript(requestUrl, _makeCallBackHandler(bid.bidId)); + } + } + + return { + callBids: _callBids + }; +}; + +module.exports = AdSupplyAdapter; diff --git a/src/adapters/analytics/aolPartnersIds.json b/src/adapters/analytics/aolPartnersIds.json index 2f7c9276cb8..c215c380e82 100644 --- a/src/adapters/analytics/aolPartnersIds.json +++ b/src/adapters/analytics/aolPartnersIds.json @@ -68,5 +68,9 @@ "innity": 67, "beachfront": 68, "trion": 69, - "huddledmasses": 70 + "huddledmasses": 70, + "pubgears": 71, + "adsupply": 72, + "quantcast": 73, + "prebidServer": 74 } diff --git a/src/adapters/aol.js b/src/adapters/aol.js index febaf222c7c..e34d69ba51b 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -36,36 +36,13 @@ const AolAdapter = function AolAdapter() { readyEventFired = true; return fn(); }; - let doScrollCheck = () => { - if (readyEventFired) { - return; - } - try { - document.documentElement.doScroll('left'); - } catch (e) { - setTimeout(doScrollCheck, 1); - return; - } - return idempotentFn(); - }; + if (document.readyState === "complete") { return idempotentFn(); } - if (document.addEventListener) { - document.addEventListener("DOMContentLoaded", idempotentFn, false); - window.addEventListener("load", idempotentFn, false); - } else if (document.attachEvent) { - document.attachEvent("onreadystatechange", idempotentFn); - window.attachEvent("onload", idempotentFn); - let topLevel = false; - try { - topLevel = window.frameElement === null; - } catch (e) { - } - if (document.documentElement.doScroll && topLevel) { - return doScrollCheck(); - } - } + + document.addEventListener("DOMContentLoaded", idempotentFn, false); + window.addEventListener("load", idempotentFn, false); }; })(); @@ -125,7 +102,7 @@ const AolAdapter = function AolAdapter() { iframe.style.display = 'none'; iframe.src = pixelsItem.src; if (document.readyState === 'interactive' || - document.readyState === 'complete') { + document.readyState === 'complete') { document.body.appendChild(iframe); } else { domReady(() => { diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 0a3b8f7284b..4dc1a368cbc 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -61,6 +61,7 @@ AppNexusAdapter = function AppNexusAdapter() { } jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + jptCall = utils.tryAppendQueryString(jptCall, 'traffic_source_code', (utils.getBidIdParameter('trafficSourceCode', bid.params))); //sizes takes a bit more logic var sizeQueryString = ''; diff --git a/src/adapters/audienceNetwork.js b/src/adapters/audienceNetwork.js index 8ab96430491..aafc3bb9edb 100644 --- a/src/adapters/audienceNetwork.js +++ b/src/adapters/audienceNetwork.js @@ -9,9 +9,7 @@ import { format } from '../url'; import { logError } from '../utils'; import { createNew } from './adapter'; -const baseAdapter = createNew('audienceNetwork'); -const setBidderCode = baseAdapter.setBidderCode; -const getBidderCode = baseAdapter.getBidderCode; +const { setBidderCode, getBidderCode } = createNew('audienceNetwork'); /** * Does this bid request contain valid parameters? @@ -25,14 +23,15 @@ const validateBidRequest = bid => Array.isArray(bid.sizes) && bid.sizes.length > 0; /** - * Does this bid request contain valid sizes? + * Return a copy of a bid with slot sizes flattened and filtered * @param {Object} bid - * @returns {Boolean} + * @returns {Object} copy of bid */ -const validateBidRequestSizes = bid => { - bid.sizes = bid.sizes.map(flattenSize); - return bid.sizes.every( size => - ['native', 'fullwidth', '300x250', '320x50'].includes(size) ); +const flattenBidRequestSizes = bid => { + const sizes = Array.isArray(bid.sizes) && bid.sizes + .map(flattenSize) + .filter(isValidSize); + return Object.assign({}, bid, { sizes }); }; /** @@ -44,6 +43,13 @@ const validateBidRequestSizes = bid => { const flattenSize = size => (Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size; +/** + * Is this a valid slot size? + * @param {String} size + * @returns {Boolean} + */ +const isValidSize = size => ['native', 'fullwidth', '300x250', '320x50'].includes(size); + /** * Does the search part of the URL contain "anhb_testmode" * and therefore indicate testmode should be used? @@ -144,8 +150,8 @@ const callBids = bidRequest => { const placementids = []; const adformats = []; bidRequest.bids + .map(flattenBidRequestSizes) .filter(validateBidRequest) - .filter(validateBidRequestSizes) .forEach( bid => bid.sizes.forEach( size => { adUnitCodes.push(bid.placementCode); placementids.push(bid.params.placementId); @@ -202,7 +208,6 @@ const callBids = bidRequest => { * @property {Function} setBidderCode - used for bidder aliasing * @property {Function} getBidderCode - unique 'audienceNetwork' identifier */ -const AudienceNetwork = () => { - return { callBids, setBidderCode, getBidderCode }; -}; +const AudienceNetwork = () => ({ callBids, setBidderCode, getBidderCode }); + module.exports = AudienceNetwork; diff --git a/src/adapters/bidfluence.js b/src/adapters/bidfluence.js index e5253e09878..bd3790e8652 100644 --- a/src/adapters/bidfluence.js +++ b/src/adapters/bidfluence.js @@ -5,7 +5,7 @@ const adloader = require('../adloader'); var BidfluenceAdapter = function BidfluenceAdapter() { - const scriptUrl = "//bidfluence.azureedge.net/forge.js"; + const scriptUrl = "//cdn.bidfluence.com/forge.js"; $$PREBID_GLOBAL$$.bfPbjsCB = function (bfr) { var bidRequest = utils.getBidRequest(bfr.cbID); diff --git a/src/adapters/prebidServer.js b/src/adapters/prebidServer.js new file mode 100644 index 00000000000..7b2c899e905 --- /dev/null +++ b/src/adapters/prebidServer.js @@ -0,0 +1,124 @@ +import Adapter from 'src/adapters/adapter'; +import bidfactory from 'src/bidfactory'; +import bidmanager from 'src/bidmanager'; +import * as utils from 'src/utils'; +import { ajax } from 'src/ajax'; +import { STATUS } from 'src/constants'; +import { queueSync, persist } from 'src/cookie.js'; + +const TYPE = 's2s'; +const cookiePersistMessage = `Your browser may be blocking 3rd party cookies. By clicking on this page you allow Prebid Server and other advertising partners to place cookies to help us advertise. You can opt out of their cookies here.`; +const cookiePersistUrl = '//ib.adnxs.com/seg?add=1&redir='; +/** + * Bidder adapter for Prebid Server + */ +function PrebidServer() { + + let baseAdapter = Adapter.createNew('prebidServer'); + let config; + + baseAdapter.setConfig = function(s2sconfig) { + config = s2sconfig; + }; + + /* Prebid executes this function when the page asks to send out bid requests */ + baseAdapter.callBids = function(bidRequest) { + + let requestJson = { + account_id : config.accountId, + tid : bidRequest.tid, + max_bids: config.maxBids, + timeout_millis : config.timeout, + url: utils.getTopWindowUrl(), + prebid_version : '$prebid.version$', + ad_units : bidRequest.ad_units.filter(hasSizes) + }; + + const payload = JSON.stringify(requestJson); + ajax(config.endpoint, handleResponse, payload, { + contentType: 'text/plain', + withCredentials : true + }); + }; + + // at this point ad units should have a size array either directly or mapped so filter for that + function hasSizes(unit) { + return unit.sizes && unit.sizes.length; + } + + /* Notify Prebid of bid responses so bids can get in the auction */ + function handleResponse(response) { + let result; + try { + result = JSON.parse(response); + + if(result.status === 'OK') { + if(result.bidder_status) { + result.bidder_status.forEach(bidder => { + if(bidder.no_bid) { + // store a "No Bid" bid response + + let bidObject = bidfactory.createBid(STATUS.NO_BID, { + bidId: bidder.bid_id + }); + bidObject.adUnitCode = bidder.ad_unit; + bidObject.bidderCode = bidder.bidder; + bidmanager.addBidResponse(bidObject.adUnitCode, bidObject); + } + if(bidder.no_cookie) { + // if no cookie is present then no bids were made, we don't store a bid response + queueSync({bidder: bidder.bidder, url : bidder.usersync.url, type : bidder.usersync.type}); + } + }); + } + if(result.bids) { + result.bids.forEach(bidObj => { + let bidRequest = utils.getBidRequest(bidObj.bid_id); + let cpm = bidObj.price; + let status; + if (cpm !== 0) { + status = STATUS.GOOD; + } else { + status = STATUS.NO_BID; + } + + let bidObject = bidfactory.createBid(status, bidRequest); + bidObject.creative_id = bidObj.creative_id; + bidObject.bidderCode = bidObj.bidder; + bidObject.cpm = cpm; + bidObject.ad = bidObj.adm; + bidObject.width = bidObj.width; + bidObject.height = bidObj.height; + + bidmanager.addBidResponse(bidObj.code, bidObject); + }); + } + } + else if (result.status === 'no_cookie') { + //cookie sync + persist(cookiePersistUrl, cookiePersistMessage); + } + } catch (error) { + utils.logError(error); + } + + if (!result || result.status && result.status.includes('Error')) { + utils.logError('error parsing response: ', result.status); + } + } + + return { + setConfig : baseAdapter.setConfig, + createNew: PrebidServer.createNew, + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + type : TYPE + }; + +} + +PrebidServer.createNew = function() { + return new PrebidServer(); +}; + +module.exports = PrebidServer; diff --git a/src/adapters/pubgears.js b/src/adapters/pubgears.js new file mode 100644 index 00000000000..6d487826a75 --- /dev/null +++ b/src/adapters/pubgears.js @@ -0,0 +1,151 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var consts = require('../constants.json'); +var utils = require('../utils.js'); +var d = document; +var SCRIPT = 'script'; +var PARAMS = 'params'; +var SIZES = 'sizes'; +var SIZE = 'size'; +var CPM = 'cpm'; +var AD = 'ad'; +var WIDTH = 'width'; +var HEIGHT = 'height'; +var PUB_ZONE = 'pub_zone'; +var GROSS_PRICE = 'gross_price'; +var RESOURCE = 'resource'; +var DETAIL = 'detail'; +var BIDDER_CODE_RESPONSE_KEY = 'bidderCode'; +var BIDDER_CODE = 'pubgears'; +var SCRIPT_ID = 'pg-header-tag'; +var ATTRIBUTE_PREFIX = 'data-bsm-'; +var SLOT_LIST_ATTRIBUTE = 'slot-list'; +var PUBLISHER_ATTRIBUTE = 'pub'; +var FLAG_ATTRIBUTE = 'flag'; +var PLACEMENT_CODE = 'placementCode'; +var BID_ID = 'bidId'; +var PUBLISHER_PARAM = 'publisherName'; +var PUB_ZONE_PARAM = 'pubZone'; +var BID_RECEIVED_EVENT_NAME = 'onBidResponse'; +var SLOT_READY_EVENT_NAME = 'onResourceComplete'; +var CREATIVE_TEMPLATE = decodeURIComponent("%3Cscript%3E%0A(function(define)%7B%0Adefine(function(a)%7B%0A%09var%20id%3D%20%22pg-ad-%22%20%2B%20Math.floor(Math.random()%20*%201e10)%2C%20d%3D%20document%0A%09d.write(\'%3Cdiv%20id%3D%22\'%2Bid%2B\'%22%3E%3C%2Fdiv%3E\')%0A%09a.push(%7B%0A%09%09pub%3A%20\'%25%25PUBLISHER_NAME%25%25\'%2C%0A%09%09pub_zone%3A%20\'%25%25PUB_ZONE%25%25\'%2C%0A%09%09sizes%3A%20%5B\'%25%25SIZE%25%25\'%5D%2C%0A%09%09flag%3A%20true%2C%0A%09%09container%3A%20d.getElementById(id)%2C%0A%09%7D)%3B%0A%7D)%7D)(function(f)%7Bvar%20key%3D\'uber_imps\'%2Ca%3Dthis%5Bkey%5D%3Dthis%5Bkey%5D%7C%7C%5B%5D%3Bf(a)%3B%7D)%3B%0A%3C%2Fscript%3E%0A%3Cscript%20src%3D%22%2F%2Fc.pubgears.com%2Ftags%2Fb%22%3E%3C%2Fscript%3E%0A"); +var TAG_URL = '//c.pubgears.com/tags/h'; +var publisher = ''; + +module.exports = PubGearsAdapter; + +function PubGearsAdapter() { + var proxy = null; + var pendingSlots = {}; + var initialized = false; + + this.callBids = callBids; + + function callBids(params) { + var bids = params[consts.JSON_MAPPING.PL_BIDS]; + var slots = bids.map(getSlotFromBidParam) ; + if(slots.length <= 0) + return; + publisher = bids[0][PARAMS][PUBLISHER_PARAM]; + + bids.forEach(function(bid) { + + var name = getSlotFromBidParam(bid); + pendingSlots[ name ] = bid; + }); + + proxy = proxy || getScript(SCRIPT_ID) || makeScript(slots, publisher, SCRIPT_ID, TAG_URL); + if(!initialized) + registerEventListeners(proxy); + initialized = true; + } + function loadScript(script) { + var anchor = (function(scripts) { + return scripts[ scripts.length - 1 ]; + })(d.getElementsByTagName(SCRIPT)); + + return anchor.parentNode.insertBefore(script, anchor); + } + function getSlotFromBidParam(bid) { + var size = getSize(bid); + var params = bid[PARAMS]; + var slotName = params[PUB_ZONE_PARAM]; + return [ slotName, size ].join('@'); + } + function getSlotFromResource(resource) { + var size = resource[SIZE]; + var key = [ resource[PUB_ZONE], size ].join('@'); + return key; + } + function getSize(bid) { + var sizes = bid[SIZES]; + var size = Array.isArray(sizes[0]) ? sizes[0] : sizes; + return size.join('x'); + } + function makeScript(slots, publisher, id, url) { + var script = d.createElement(SCRIPT); + script.src = url; + script.id = id ; + script.setAttribute(ATTRIBUTE_PREFIX + SLOT_LIST_ATTRIBUTE, slots.join(' ')); + script.setAttribute(ATTRIBUTE_PREFIX + FLAG_ATTRIBUTE, 'true'); + script.setAttribute(ATTRIBUTE_PREFIX + PUBLISHER_ATTRIBUTE, publisher); + + return loadScript(script); + } + function getScript(id) { + return d.getElementById(id); + } + function registerEventListeners(script) { + script.addEventListener(BID_RECEIVED_EVENT_NAME, onBid, true); + script.addEventListener(SLOT_READY_EVENT_NAME, onComplete, true); + } + function onBid(event) { + var data = event[DETAIL]; + var slotKey = getSlotFromResource(data[RESOURCE]); + var bidRequest = pendingSlots[slotKey]; + var adUnitCode = bidRequest[PLACEMENT_CODE]; + var bid = null; + + if(bidRequest) { + bid = buildResponse(data, bidRequest); + bidmanager.addBidResponse(adUnitCode, bid); + utils.logMessage('adding bid respoonse to "' + adUnitCode + '" for bid request "' + bidRequest[BID_ID] + '"'); + }else { + utils.logError('Cannot get placement id for slot "' + slotKey + '"'); + } + } + function buildResponse(eventData, bidRequest) { + var resource = eventData[RESOURCE]; + var dims = resource[SIZE].split('x'); + var price = Number(eventData[GROSS_PRICE]); + var status = isNaN(price) || price <= 0 ? 2 : 1; + + var response = bidfactory.createBid(status, bidRequest); + response[BIDDER_CODE_RESPONSE_KEY] = BIDDER_CODE; + + if(status !== 1) + return response; + + response[AD] = getCreative(resource); + + response[CPM] = price / 1e3 ; + response[WIDTH] = dims[0]; + response[HEIGHT] = dims[1]; + return response; + } + function getCreative(resource) { + var token = '%%'; + var creative = CREATIVE_TEMPLATE; + var replacementValues = { + publisher_name: publisher, + pub_zone: resource[PUB_ZONE], + size: resource[SIZE] + }; + return utils.replaceTokenInString(creative, replacementValues, token); + } + function onComplete(event) { + var data = event[DETAIL]; + var slotKey = getSlotFromResource(data[RESOURCE]); + delete pendingSlots[slotKey]; + } +} diff --git a/src/adapters/quantcast.js b/src/adapters/quantcast.js new file mode 100644 index 00000000000..cbf6f1c9028 --- /dev/null +++ b/src/adapters/quantcast.js @@ -0,0 +1,135 @@ +const utils = require('../utils.js'); +const bidfactory = require('../bidfactory.js'); +const bidmanager = require('../bidmanager.js'); +const ajax = require('../ajax.js'); +const CONSTANTS = require('../constants.json'); +const QUANTCAST_CALLBACK_URL = 'http://global.qc.rtb.quantserve.com:8080/qchb'; + +var QuantcastAdapter = function QuantcastAdapter() { + + const BIDDER_CODE = 'quantcast'; + + const DEFAULT_BID_FLOOR = 0.0000000001; + let bidRequests = {}; + + let returnEmptyBid = function(bidId) { + var bidRequested = utils.getBidRequest(bidId); + if (!utils.isEmpty(bidRequested)) { + let bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequested); + bid.bidderCode = BIDDER_CODE; + bidmanager.addBidResponse(bidRequested.placementCode, bid); + } + return; + }; + + + //expose the callback to the global object: + $$PREBID_GLOBAL$$.handleQuantcastCB = function (responseText) { + if(utils.isEmpty(responseText)) { + return; + } + let response = null; + try { + response = JSON.parse(responseText); + } catch(e) { + // Malformed JSON + utils.logError("Malformed JSON received from server - can't do anything here"); + return; + } + + if(response === null || !response.hasOwnProperty('bids') || utils.isEmpty(response.bids)) { + utils.logError("Sub-optimal JSON received from server - can't do anything here"); + return; + } + + for(let i = 0; i < response.bids.length; i++) { + let seatbid = response.bids[i]; + let key = seatbid.placementCode; + var request = bidRequests[key]; + if(request === null || request === undefined) { + return returnEmptyBid(seatbid.placementCode); + } + // This line is required since this is the field + // that bidfactory.createBid looks for + request.bidId = request.imp[0].placementCode; + let responseBid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, request); + + responseBid.cpm = seatbid.cpm; + responseBid.ad = seatbid.ad; + responseBid.height = seatbid.height; + responseBid.width = seatbid.width; + responseBid.bidderCode = response.bidderCode; + responseBid.requestId = request.requestId; + responseBid.bidderCode = BIDDER_CODE; + + bidmanager.addBidResponse(request.bidId, responseBid); + } + + }; + + function callBids(params) { + let bids = params.bids || []; + if (bids.length === 0) { + return; + } + + let referrer = utils.getTopWindowUrl(); + let loc = utils.getTopWindowLocation(); + let domain = loc.hostname; + let publisherId = 0; + + publisherId = '' + bids[0].params.publisherId; + utils._each(bids, function(bid) { + let key = bid.placementCode; + var bidSizes = []; + utils._each(bid.sizes, function (size) { + bidSizes.push({ + 'width' : size[0], + 'height' : size[1] + }); + }); + + bidRequests[key] = bidRequests[key] || { + 'publisherId' : publisherId, + 'requestId' : bid.bidId, + 'bidId' : bid.bidId, + 'site' : { + 'page' : loc.href, + 'referrer' : referrer, + 'domain' : domain, + }, + 'imp' : [{ + + 'banner' : { + 'battr' : bid.params.battr, + 'sizes' : bidSizes, + }, + 'placementCode' : bid.placementCode, + 'bidFloor' : bid.params.bidFloor || DEFAULT_BID_FLOOR, + }] + }; + + utils._each(bidRequests, function (bidRequest) { + ajax.ajax(QUANTCAST_CALLBACK_URL, $$PREBID_GLOBAL$$.handleQuantcastCB, JSON.stringify(bidRequest), { + method : 'POST', + withCredentials: true + }); + }); + }); + } + + + // Export the `callBids` function, so that Prebid.js can execute + // this function when the page asks to send out bid requests. + return { + callBids: callBids, + QUANTCAST_CALLBACK_URL: QUANTCAST_CALLBACK_URL + }; +}; + +exports.createNew = function() { + return new QuantcastAdapter(); +}; + + +module.exports = QuantcastAdapter; diff --git a/src/adapters/rhythmone.js b/src/adapters/rhythmone.js index 57b6e29a217..c8e81d110e3 100644 --- a/src/adapters/rhythmone.js +++ b/src/adapters/rhythmone.js @@ -4,34 +4,27 @@ var bidmanager = require('../bidmanager.js'), import {ajax as ajax} from '../ajax'; -function track(debug) { - if(debug === true){ - //console.log('GA: %s %s %s', p1, p2, p3 || ''); - } -} - -var w = (typeof window !== "undefined" ? window : {}); -w.trackR1Impression = track; - module.exports = function(bidManager, global, loader){ var version = "0.9.0.0", defaultZone = "1r", defaultPath = "mvo", debug = false, - auctionEnded = false, requestCompleted = false, - placementCodes = {}; - + placementCodes = {}, + loadStart, + configuredPlacements = [], + fat = /(^v|(\.0)+$)/gi; + if(typeof global === "undefined") global = window; - + if(typeof bidManager === "undefined") bidManager = bidmanager; - + if(typeof loader === "undefined") loader = ajax; - + function applyMacros(txt, values){ return txt.replace(/\{([^\}]+)\}/g, function(match){ var v = values[match.replace(/[\{\}]/g, "").toLowerCase()]; @@ -39,7 +32,7 @@ module.exports = function(bidManager, global, loader){ return match; }); } - + function load(bidParams, url, callback){ loader(url, function(responseText, response){ if(response.status === 200) @@ -56,7 +49,7 @@ module.exports = function(bidManager, global, loader){ t = "application/x-shockwave-flash", x = global.ActiveXObject; - if(p && + if(p && p["Shockwave Flash"] && m && m[t] && @@ -70,88 +63,58 @@ module.exports = function(bidManager, global, loader){ return false; } - - var bidderCode = "rhythmone", - bidLostTimeout = null; - + + var bidderCode = "rhythmone"; + function attempt(valueFunction, defaultValue){ try{ return valueFunction(); }catch(ex){} return defaultValue; } - + function logToConsole(txt){ if(debug) console.log(txt); } - - function sniffAuctionEnd(){ - - global.$$PREBID_GLOBAL$$.onEvent('bidWon', function (e) { - - if(e.bidderCode === bidderCode){ - placementCodes[e.adUnitCode] = true; - track(debug, 'hb', "bidWon"); - } - - if(auctionEnded){ - clearTimeout(bidLostTimeout); - bidLostTimeout = setTimeout(function(){ - for(var k in placementCodes) - if(placementCodes[k] === false) - track(debug, 'hb', "bidLost"); - }, 50); - } - }); - - global.$$PREBID_GLOBAL$$.onEvent('auctionEnd', function () { - - auctionEnded = true; - if(requestCompleted === false) - track(debug, 'hb', 'rmpReplyFail', "prebid timeout post auction"); - }); - } - function getBidParameters(bids){ for(var i=0;i 0) return d[d.length-1]; - return global.top.document.location.hostname; + return global.top.document.location.hostname; // try/catch is in the attempt function },"")); - p("title", attempt(function(){return global.top.document.title;},"")); + p("title", attempt(function(){return global.top.document.title;},"")); // try/catch is in the attempt function p("url", attempt(function(){ var l; - try{l = global.top.document.location.href.toString();} + try{l = global.top.document.location.href.toString();} // try/catch is in the attempt function catch(ex){l = global.document.location.href.toString();} return l; },"")); @@ -183,106 +146,128 @@ module.exports = function(bidManager, global, loader){ p("tz", (new Date()).getTimezoneOffset()); p("dtype", ((/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent) ? 1 : ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent) ? 3 : 2))); p("flash", (flashInstalled() ? 1 : 0)); - - var placementCodes = [], - heights = [], + + var heights = [], widths = [], floors = [], mediaTypes = [], - fat = /(^v|(\.0)+$)/gi, i=0; + configuredPlacements = []; + p("hbv", global.$$PREBID_GLOBAL$$.version.replace(fat,"")+","+version.replace(fat,"")); - + for(; i 0 && typeof bids[i].sizes[0] === "number") bids[i].sizes = [bids[i].sizes]; - + for(var j = 0; j 0){ + data.ancestor_origins = ao[ao.length-1]; + } + + data.popped = window.opener!==null?1:0; + data.framed = window.top===window?0:1; + + try{ + data.url = window.top.document.location.href.toString(); + }catch(ex){ + data.url = window.document.location.href.toString(); + } + + var prebid_instance = global.$$PREBID_GLOBAL$$; + + data.prebid_version = prebid_instance.version.replace(fat,""); + data.response_ms = (new Date()).getTime() - loadStart; + data.placement_codes = configuredPlacements.join(","); + data.bidder_version = version; + data.prebid_timeout = prebid_instance.cbTimeout || prebid_instance.bidderTimeout; + + for(var k in data){ + q.push(encodeURIComponent(k)+"="+encodeURIComponent((typeof data[k] === "object" ? JSON.stringify(data[k]) : data[k]))); + } + + q.sort(); + i.src = u+q.join("&"); + } + this.callBids = function(params){ var slotMap = {}, bidParams = getBidParameters(params.bids); - + debug = (bidParams !== null && bidParams.debug === true); - - track(debug, 'hb', 'callBids'); if(bidParams === null){ noBids(params); - track(debug, 'hb', 'misconfiguration'); return; } - sniffAuctionEnd(); - - track(debug, 'hb', 'rmpRequest'); - for(var i = 0; i bidderRequest.bidderCode === 'yieldbot').bids - .find(bid => bid.bidId === v) || {}; - slot = adapterConfig.params.slot || ''; - criteria = yieldbot.getSlotCriteria(slot); + ybRequest = $$PREBID_GLOBAL$$._bidsRequested + .find(bidderRequest => bidderRequest.bidderCode === 'yieldbot'); - placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var bid = ybotlib.buildBid(criteria); + adapterConfig = ybRequest && ybRequest.bids ? ybRequest.bids.find(bid => bid.bidId === v) : null; - bidmanager.addBidResponse(placementCode, bid); + if (adapterConfig && adapterConfig.params && adapterConfig.params.slot) { + var placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; + var criteria = yieldbot.getSlotCriteria(adapterConfig.params.slot); + var bid = ybotlib.buildBid(criteria); + bidmanager.addBidResponse(placementCode, bid); + } }); } }; diff --git a/src/adloader.js b/src/adloader.js index 60ea3843625..e361d418576 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -83,23 +83,3 @@ function requestResource(tagSrc, callback) { elToAppend.insertBefore(jptScript, elToAppend.firstChild); } } - -//track a impbus tracking pixel -//TODO: Decide if tracking via AJAX is sufficent, or do we need to -//run impression trackers via page pixels? -exports.trackPixel = function (pixelUrl) { - let delimiter; - let trackingPixel; - - if (!pixelUrl || typeof (pixelUrl) !== 'string') { - utils.logMessage('Missing or invalid pixelUrl.'); - return; - } - - delimiter = pixelUrl.indexOf('?') > 0 ? '&' : '?'; - - //add a cachebuster so we don't end up dropping any impressions - trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); - (new Image()).src = trackingPixel; - return trackingPixel; -}; diff --git a/src/ajax.js b/src/ajax.js index 5d0f34c578d..42c363dbdf6 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -83,6 +83,9 @@ export function ajax(url, callback, data, options = {}) { if (options.withCredentials) { x.withCredentials = true; } + utils._each(options.customHeaders, (value, header) => { + x.setRequestHeader(header, value); + }); if (options.preflight) { x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); } diff --git a/src/bidmanager.js b/src/bidmanager.js index 5163d23bc31..ea6b41f5203 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -1,4 +1,4 @@ -import {uniques, flatten, adUnitsFilter} from './utils'; +import { uniques, flatten, adUnitsFilter, getBidderRequest } from './utils'; import {getPriceBucketString} from './cpmBucketManager'; var CONSTANTS = require('./constants.json'); @@ -84,13 +84,6 @@ exports.bidsBackAll = function () { return bidsBackAll(); }; -function getBidderRequest(bidder, adUnitCode) { - return $$PREBID_GLOBAL$$._bidsRequested.find(request => { - return request.bids - .filter(bid => bid.bidder === bidder && bid.placementCode === adUnitCode).length > 0; - }) || { start: null, requestId: null }; -} - /* * This function should be called to by the bidder adapter to register a bid response */ @@ -107,7 +100,7 @@ exports.addBidResponse = function (adUnitCode, bid) { requestId: requestId, responseTimestamp: timestamp(), requestTimestamp: start, - cpm: bid.cpm || 0, + cpm: parseFloat(bid.cpm) || 0, bidder: bid.bidderCode, adUnitCode }); diff --git a/src/constants.json b/src/constants.json index 47ce9af55e5..4cf0aac447d 100644 --- a/src/constants.json +++ b/src/constants.json @@ -59,5 +59,8 @@ "hb_pb", "hb_size", "hb_deal" - ] + ], + "S2S" : { + "DEFAULT_ENDPOINT" : "https://prebid.adnxs.com/pbs/v1/auction" + } } diff --git a/src/cookie.js b/src/cookie.js new file mode 100644 index 00000000000..8e823b1acd9 --- /dev/null +++ b/src/cookie.js @@ -0,0 +1,85 @@ +const cookie = exports; +import * as utils from 'utils'; + +const queue = []; + +function fireSyncs() { + queue.forEach(obj => { + utils.logMessage(`Invoking cookie sync for bidder: ${obj.bidder}`); + if(obj.type === 'iframe') { + utils.insertCookieSyncIframe(obj.url, false); + } else { + utils.insertPixel(obj.url); + } + }); + //empty queue. + queue.length = 0; +} + +/** + * Add this bidder to the queue for sync + * @param {String} bidder bidder code + * @param {String} url optional URL for invoking cookie sync if provided. + */ +cookie.queueSync = function ({bidder, url, type}) { + queue.push({bidder, url, type}); +}; + +/** + * Fire cookie sync URLs previously queued + * @param {number} timeout time in ms to delay in sending + */ +cookie.syncCookies = function(timeout) { + if(timeout) { + setTimeout(fireSyncs, timeout); + } + else { + fireSyncs(); + } +}; + +cookie.persist = function(url, msgHtml) { + if(!utils.isSafariBrowser()){ + return; + } + linkOverride(url); + displayFooter(msgHtml); +}; + +function linkOverride(url) { + for (var i = 0; i < document.links.length; i++){ + var link = document.links[i]; + link.href = url + encodeURIComponent(link.href); + } + } + +function displayFooter(msgHtml) { + // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_3_Do_something_only_once + if (document.cookie.replace(/(?:(?:^|.*;\s*)pbsCookiePersistFooter\s*\=\s*([^;]*).*$)|^.*$/, '$1') !== 'true') { + document.body.appendChild(createFooter(msgHtml)); + document.cookie = 'pbsCookiePersistFooter=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'; + } +} + +function createFooter(msgHtml) { + const footer = document.createElement('div'); + footer.style.background = '#D3D3D3'; + footer.style.color = '#555'; + footer.style.boxShadow = '0 -1px 2px rgba(0, 0, 0, 0.2)'; + footer.style.fontFamily = 'sans-serif'; + footer.style.lineHeight = '1.5'; + footer.style.position = 'fixed'; + footer.style.bottom = '0'; + footer.style.left = '0'; + footer.style.right = '0'; + footer.style.width = '100%'; + footer.style.padding = '1em 0'; + footer.style.zindex = '1000'; + + const footerText = document.createElement('p'); + footerText.style.margin = '0 2em'; + footerText.innerHTML = msgHtml; + footer.appendChild(footerText); + + return footer; +} diff --git a/src/prebid.js b/src/prebid.js index f5b5664b9ef..5ec347b8327 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -7,8 +7,10 @@ import 'polyfill'; import {parse as parseURL, format as formatURL} from './url'; import {isValidePriceConfig} from './cpmBucketManager'; import {listenMessagesFromCreative} from './secure-creatives'; +import { syncCookies } from 'src/cookie.js'; import { loadScript } from './adloader'; + var $$PREBID_GLOBAL$$ = getGlobal(); var CONSTANTS = require('./constants.json'); var utils = require('./utils.js'); @@ -70,6 +72,10 @@ utils.logInfo('Prebid.js v$prebid.version$ loaded'); //create adUnit array $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; +//delay to request cookie sync to stay out of critical path +$$PREBID_GLOBAL$$.cookieSyncDelay = $$PREBID_GLOBAL$$.cookieSyncDelay || 100; + + /** * Command queue that functions will execute once prebid.js is loaded * @param {function} cmd Anonymous function to execute @@ -289,18 +295,21 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { if (doc && id) { try { //lookup ad by ad Id - var adObject = $$PREBID_GLOBAL$$._bidsReceived.find(bid => bid.adId === id); - if (adObject) { + const bid = $$PREBID_GLOBAL$$._bidsReceived.find(bid => bid.adId === id); + if (bid) { + //replace macros according to openRTB with price paid = bid.cpm + bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); + bid.url = utils.replaceAuctionPrice(bid.url, bid.cpm); //save winning bids - $$PREBID_GLOBAL$$._winningBids.push(adObject); + $$PREBID_GLOBAL$$._winningBids.push(bid); //emit 'bid won' event here - events.emit(BID_WON, adObject); + events.emit(BID_WON, bid); - const { height, width, ad, mediaType, adUrl: url, renderer } = adObject; + const { height, width, ad, mediaType, adUrl: url, renderer } = bid; if (renderer && renderer.url) { - renderer.render(adObject); + renderer.render(bid); } else if ((doc === document && !utils.inIframe()) || mediaType === 'video') { utils.logError(`Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`); } else if (ad) { @@ -346,6 +355,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { $$PREBID_GLOBAL$$.clearAuction = function() { auctionRunning = false; + syncCookies($$PREBID_GLOBAL$$.cookieSyncDelay); utils.logMessage('Prebid auction cleared'); if (bidRequestQueue.length) { bidRequestQueue.shift()(); @@ -718,5 +728,32 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; +/** + * Set config for server to server header bidding + * @param {object} options - config object for s2s + */ +$$PREBID_GLOBAL$$.setS2SConfig = function(options) { + + if (!utils.contains(Object.keys(options), 'accountId')) { + utils.logError('accountId missing in Server to Server config'); + return; + } + + if (!utils.contains(Object.keys(options), 'bidders')) { + utils.logError('bidders missing in Server to Server config'); + return; + } + + const config = Object.assign({ + enabled : false, + endpoint : CONSTANTS.S2S.DEFAULT_ENDPOINT, + timeout : 1000, + maxBids : 1, + adapter : 'prebidServer' + }, options); + adaptermanager.setS2SConfig(config); +}; + + $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); processQue(); diff --git a/src/sizeMapping.js b/src/sizeMapping.js index 46cca5cb668..90ce5b68851 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -14,7 +14,7 @@ function mapSizes(adUnit) { const mapping = adUnit.sizeMapping.reduce((prev, curr) => { return prev.minWidth < curr.minWidth ? curr : prev; }); - if(mapping.sizes) { + if(mapping.sizes && mapping.sizes.length) { return mapping.sizes; } return adUnit.sizes; @@ -23,7 +23,7 @@ function mapSizes(adUnit) { const mapping = adUnit.sizeMapping.find(sizeMapping =>{ return width > sizeMapping.minWidth; }); - if(mapping && mapping.sizes){ + if(mapping && mapping.sizes && mapping.sizes.length){ sizes = mapping.sizes; utils.logMessage(`AdUnit : ${adUnit.code} resized based on device width to : ${sizes}`); } diff --git a/src/utils.js b/src/utils.js index a4a31c874b2..2ff853920fd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -438,6 +438,47 @@ var hasOwn = function (objectToCheck, propertyToCheckFor) { return (typeof objectToCheck[propertyToCheckFor] !== 'undefined') && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); } }; + +var insertElement = function(elm) { + let elToAppend = document.getElementsByTagName('head'); + try{ + elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); + if (elToAppend.length) { + elToAppend = elToAppend[0]; + elToAppend.insertBefore(elm, elToAppend.firstChild); + } + } catch (e) {} +}; + +exports.insertPixel = function (url) { + const img = new Image(); + img.id = this.getUniqueIdentifierStr(); + img.src = url; + img.height = 0; + img.width = 0; + img.style.display = 'none'; + img.onload = function() { + try { + this.parentNode.removeChild(this); + } catch(e) { + } + }; + insertElement(img); +}; + +/** + * Inserts empty iframe with the specified `url` for cookie sync + * @param {string} url URL to be requested + * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true + */ +exports.insertCookieSyncIframe = function(url, encodeUri) { + let iframeHtml = this.createTrackPixelIframeHtml(url, encodeUri); + let div = document.createElement('div'); + div.innerHTML = iframeHtml; + let iframe = div.firstChild; + insertElement(iframe); +}; + /** * Creates a snippet of HTML that retrieves the specified `url` * @param {string} url URL to be requested @@ -457,14 +498,18 @@ exports.createTrackPixelHtml = function (url) { /** * Creates a snippet of Iframe HTML that retrieves the specified `url` * @param {string} url plain URL to be requested + * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @return {string} HTML snippet that contains the iframe src = set to `url` */ -exports.createTrackPixelIframeHtml = function (url) { +exports.createTrackPixelIframeHtml = function (url, encodeUri = true) { if (!url) { return ''; } + if(encodeUri) { + url = encodeURI(url); + } - return ``; + return ``; }; /** @@ -597,4 +642,20 @@ export function inIframe() { } catch (e) { return true; } -} \ No newline at end of file +} + +export function isSafariBrowser() { + return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); +} + +export function replaceAuctionPrice(str, cpm) { + if(!str) return; + return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); +} + +export function getBidderRequest(bidder, adUnitCode) { + return $$PREBID_GLOBAL$$._bidsRequested.find(request => { + return request.bids + .filter(bid => bid.bidder === bidder && bid.placementCode === adUnitCode).length > 0; + }) || { start: null, requestId: null }; +} diff --git a/test/spec/adapters/adsupply_spec.js b/test/spec/adapters/adsupply_spec.js new file mode 100644 index 00000000000..0798f9821d7 --- /dev/null +++ b/test/spec/adapters/adsupply_spec.js @@ -0,0 +1,359 @@ +describe('adsupply adapter tests', function () { + + const expect = require('chai').expect; + + const AdSupplyAdapter = require('../../../src/adapters/adsupply'); + const adloader = require('../../../src/adloader'); + const bidmanager = require('../../../src/bidmanager'); + const CONSTANTS = require('../../../src/constants.json'); + let adsupplyAdapter = new AdSupplyAdapter(); + + //before(() => sinon.stub(document.body, 'appendChild')); + //after(() => document.body.appendChild.restore()); + + it('adsupply response handler should exist and be a function', function () { + expect(pbjs.adSupplyResponseHandler).to.exist.and.to.be.a('function'); + }); + + it('two requests are sent to adsupply engine', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + zoneId: 111, + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }, + { + placementCode: "pc2", + bidder: "adsupply", + bidId: 'bidId2', + params: { + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + zoneId: 222, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.calledTwice(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('zoneId is not a number and not specified', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + zoneId: '111', + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }, + { + placementCode: "pc2", + bidder: "adsupply", + bidId: 'bidId2', + params: { + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.notCalled(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('siteId is empty and not specified', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + zoneId: 111, + siteId: '', + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + endpointUrl: 'engine.4dsply.com' + } + }, + { + placementCode: "pc2", + bidder: "adsupply", + bidId: 'bidId2', + params: { + zoneId: 222, + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.notCalled(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('endpointUrl is empty and not specified', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + zoneId: 111, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: '' + } + }, + { + placementCode: "pc2", + bidder: "adsupply", + bidId: 'bidId2', + params: { + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + zoneId: 222, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + } + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.notCalled(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('clientId is empty and not specified', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: '', + zoneId: 111, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }, + { + placementCode: "pc2", + bidder: "adsupply", + bidId: 'bidId2', + params: { + zoneId: 222, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.notCalled(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('parameters are missed', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1' + }] + }; + + adsupplyAdapter.callBids(request); + + sinon.assert.notCalled(stubLoadScript); + + adloader.loadScript.restore(); + }); + + it('Parameters added to the request url', function () { + let stubLoadScript = sinon.stub(adloader, 'loadScript'); + + let request = { + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + zoneId: 111, + clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + adsupplyAdapter.callBids(request); + + var requestUrl = stubLoadScript.getCall(0).args[0]; + expect(requestUrl).to.contain('111'); + expect(requestUrl).to.contain('0ab16161-a1de-4683-8837-c420bd4387c0'); + expect(requestUrl).to.contain('engine.4dsply.com'); + expect(requestUrl).to.contain('&hbt=1'); + expect(requestUrl).to.contain('g32db6906-55f4-42b1-a7d2-7dfaddce96fd'); + + adloader.loadScript.restore(); + }); + + it('Response handler invalid data', function () { + let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + // adapter needs to be called, in order for the stub to register. + new AdSupplyAdapter(); + + // bidId is not valid + pbjs.adSupplyResponseHandler(null); + + // bidRequest object is not found + pbjs.adSupplyResponseHandler('bidId1'); + + let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; + //Zone property is not found + let bidderRequest = { + bidderCode: 'adsupply', + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: clientId, + zoneId: 111, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + pbjs._bidsRequested.push(bidderRequest); + pbjs.adSupplyResponseHandler('bidId1'); + + //Media is not found + window[clientId] = window[clientId] || {}; + window[clientId]['b111'] = window[clientId]['b111'] || {}; + pbjs.adSupplyResponseHandler('bidId1'); + + sinon.assert.notCalled(stubAddBidResponse); + + pbjs._bidsRequested.pop(); + bidmanager.addBidResponse.restore(); + }); + + it('No Fill response', function () { + let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + // adapter needs to be called, in order for the stub to register. + new AdSupplyAdapter(); + + let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; + //Zone property is not found + let bidderRequest = { + bidderCode: 'adsupply', + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: clientId, + zoneId: 111, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + pbjs._bidsRequested.push(bidderRequest); + + window[clientId] = window[clientId] || {}; + window[clientId]['b111'] = window[clientId]['b111'] || {}; + window[clientId]['b111'].Media = { width: 300 }; + pbjs.adSupplyResponseHandler('bidId1'); + + sinon.assert.calledOnce(stubAddBidResponse); + + let bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; + let bidResponse = stubAddBidResponse.getCall(0).args[1]; + expect(bidPlacementCode).to.equal('pc1'); + expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bidResponse.bidderCode).to.equal('adsupply'); + + pbjs._bidsRequested.pop(); + bidmanager.addBidResponse.restore(); + }); + + it('Fill response', function () { + let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + // adapter needs to be called, in order for the stub to register. + new AdSupplyAdapter(); + + let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; + //Zone property is not found + let bidderRequest = { + bidderCode: 'adsupply', + bids: [{ + placementCode: "pc1", + bidder: "adsupply", + bidId: 'bidId1', + params: { + clientId: clientId, + zoneId: 111, + siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', + endpointUrl: 'engine.4dsply.com' + } + }] + }; + + pbjs._bidsRequested.push(bidderRequest); + + window[clientId] = window[clientId] || {}; + window[clientId]['b111'] = window[clientId]['b111'] || {}; + window[clientId]['b111'].Media = { Width: 300, Height: 250, Url: '/Redirect.engine', Ecpm: 0.0012 }; + pbjs.adSupplyResponseHandler('bidId1'); + + sinon.assert.calledOnce(stubAddBidResponse); + + let bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; + let bidResponse = stubAddBidResponse.getCall(0).args[1]; + expect(bidPlacementCode).to.equal('pc1'); + expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidResponse.bidderCode).to.equal('adsupply'); + + pbjs._bidsRequested.pop(); + bidmanager.addBidResponse.restore(); + }); +}); diff --git a/test/spec/adapters/appnexus_spec.js b/test/spec/adapters/appnexus_spec.js new file mode 100644 index 00000000000..c29bd3dfdbe --- /dev/null +++ b/test/spec/adapters/appnexus_spec.js @@ -0,0 +1,43 @@ +import {expect} from 'chai'; +import Adapter from '../../../src/adapters/appnexus'; +import bidManager from '../../../src/bidmanager'; +import adLoader from '../../../src/adloader'; + +describe('AppNexus Adapter', () => { + + let adapter; + + const REQUEST = { + 'bidderCode': 'appnexus', + 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + 'bidderRequestId': '7101db09af0db2', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'trafficSourceCode' : 'source' + }, + 'placementCode': '/19968336/header-bid-tag1', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6' + } + ], + 'start': 1469479810130 + }; + + sinon.stub(bidManager, 'addBidResponse'); + const adLoaderStub = sinon.stub(adLoader, 'loadScript'); + + describe('callBids', () => { + adapter = new Adapter(); + adapter.callBids(REQUEST); + expect(adLoaderStub.getCall(0).args[0]).to.contain('traffic_source_code=source'); + }); + +}); diff --git a/test/spec/adapters/audienceNetwork_spec.js b/test/spec/adapters/audienceNetwork_spec.js index 09553b80305..f1c528521b8 100644 --- a/test/spec/adapters/audienceNetwork_spec.js +++ b/test/spec/adapters/audienceNetwork_spec.js @@ -261,6 +261,51 @@ describe('AudienceNetwork adapter', () => { expect(logError.called).to.equal(false, 'logError called'); }); + it('filters invalid slot sizes', () => { + // Valid response + server.respondWith(JSON.stringify({ + errors: [], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } + })); + // Request bids + AudienceNetwork().callBids({ + bidderCode, + bids: [{ + bidder: bidderCode, + placementCode, + params: { placementId }, + sizes: ['350x200'] + }, { + bidder: bidderCode, + placementCode, + params: { placementId }, + sizes: ['300x250'] + }] + }); + server.respond(); + // Verify attempt to call addBidResponse + expect(addBidResponse.calledOnce).to.equal(true); + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][0]).to.equal(placementCode); + // Verify bidResponse Object + const bidResponse = addBidResponse.args[0][1]; + expect(bidResponse.getStatusCode()).to.equal(STATUS.GOOD); + expect(bidResponse.cpm).to.equal(1.23); + expect(bidResponse.bidderCode).to.equal(bidderCode); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + // Verify no attempt to log error + expect(logError.called).to.equal(false, 'logError called'); + }); + it('valid multiple bids in response', () => { const placementIdNative = 'test-placement-id-native'; const placementIdIab = 'test-placement-id-iab'; diff --git a/test/spec/adapters/bidfluence_spec.js b/test/spec/adapters/bidfluence_spec.js index 7c414853056..d6851f4fac2 100644 --- a/test/spec/adapters/bidfluence_spec.js +++ b/test/spec/adapters/bidfluence_spec.js @@ -10,8 +10,8 @@ describe('Bidfluence Adapter', () => { bids: [{ bidder: 'bidfluence', params: { - pubId: "747efe9c-5f8a-4b6e-872b-8e9939816298", - adunitId: "c4bbd807-7d22-485f-80f1-ba008ef1c619" + pubId: "test", + adunitId: "test" } }] }; diff --git a/test/spec/adapters/prebidServer_spec.js b/test/spec/adapters/prebidServer_spec.js new file mode 100644 index 00000000000..ec62794207c --- /dev/null +++ b/test/spec/adapters/prebidServer_spec.js @@ -0,0 +1,125 @@ +import { expect } from 'chai'; +import Adapter from 'src/adapters/prebidServer'; +import bidmanager from 'src/bidmanager'; +import CONSTANTS from 'src/constants.json'; + +const REQUEST = { + "account_id": "1", + "tid": "437fbbf5-33f5-487a-8e16-a7112903cfe5", + "max_bids": 1, + "timeout_millis": 1000, + "url": "", + "prebid_version": "0.21.0-pre", + "ad_units": [ + { + "code": "div-gpt-ad-1460505748561-0", + "sizes": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "transactionId": "4ef956ad-fd83-406d-bd35-e4bb786ab86c", + "bids": [ + { + "bid_id" : "123", + "bidder": "appnexus", + "params": { + "placementId": "10433394" + } + } + ] + } + ] +}; + +const RESPONSE = { + "tid": "437fbbf5-33f5-487a-8e16-a7112903cfe5", + "status": "OK", + "bidder_status": [ + { + "bidder": "appnexus", + "response_time_ms": 52, + "num_bids": 1 + } + ], + "bids": [ + { + "bid_id": "123", + "code": "div-gpt-ad-1460505748561-0", + "creative_id": "29681110", + "bidder": "appnexus", + "price": 0.5, + "adm": "", + "width": 300, + "height": 250 + } + ] +}; + +describe('S2S Adapter', () => { + + let adapter; + + beforeEach(() => adapter = Adapter.createNew()); + + describe('request function', () => { + + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + + }); + + describe('response handler', () => { + + let server; + let config = { + accountId : '1', + enabled : true, + bidders : ['appnexus'], + timeout : 1000, + endpoint : CONSTANTS.S2S.DEFAULT_ENDPOINT + }; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('registers bids', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.setConfig(config); + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const response = bidmanager.addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('cpm', 0.5); + }); + + }); + +}); diff --git a/test/spec/adapters/pubgears_spec.js b/test/spec/adapters/pubgears_spec.js new file mode 100644 index 00000000000..329d74bb5b3 --- /dev/null +++ b/test/spec/adapters/pubgears_spec.js @@ -0,0 +1,298 @@ +import { expect } from 'chai'; +import Adapter from 'src/adapters/pubgears' +import bidmanager from 'src/bidmanager' + +describe('PubGearsAdapter', () => { + + var adapter, mockScript + , params = { + bids: [] + } + + beforeEach( () => { + adapter = new Adapter() + mockScript = document.createElement('script') + sinon.spy(mockScript, 'setAttribute') + } ) + + describe('request function', () => { + + beforeEach( () => { + sinon.spy(document, 'createElement') + }) + + afterEach( () => { + document.createElement.restore && document.createElement.restore() + var s = document.getElementById('pg-header-tag') + if(s) + s.parentNode.removeChild(s) + } ) + + it('has `#callBids()` method', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + + it('requires bids to make script', () => { + + adapter.callBids({bids: []}) + expect(document.createElement.notCalled).to.be.ok + }) + + it('creates script when passed bids', () => { + + adapter.callBids({ + bidderCode: "pubgears", + bids: [ + { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + ] + }) + + sinon.assert.calledWith(document.createElement, 'script') + }) + + it('should assign attributes to script', () => { + + adapter.callBids({ + bidderCode: "pubgears", + bids: [ + { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + }, + { + bidder: "pubgears", + sizes: [ [160,600] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + ] + }) + var script = document.createElement.returnValues[0] + var slots = script.getAttribute('data-bsm-slot-list') + expect(slots).to.equal('testpub.com/combined@300x250 testpub.com/combined@160x600') + expect(script.getAttribute('data-bsm-flag')).to.equal('true') + expect(script.getAttribute('data-bsm-pub')).to.equal('integration') + expect(script.getAttribute('src')).to.equal('//c.pubgears.com/tags/h') + expect(script.id).to.equal('pg-header-tag') + }) + + it('should reuse existing script when called twice', () => { + + var params = { + bidderCode: "pubgears", + bids: [ + { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + }, + { + bidder: "pubgears", + sizes: [ [160,600] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + ] + } + adapter.callBids(params) + expect(document.createElement.calledOnce).to.be.true + adapter.callBids(params) + expect(document.createElement.calledOnce).to.be.true + }) + + it('should register event listeners', () => { + + var script = document.createElement('script') + script.id = 'pg-header-tag' + var spy = sinon.spy(script, 'addEventListener') + document.body.appendChild(script) + var params = { + bidderCode: "pubgears", + bids: [ + { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + }, + { + bidder: "pubgears", + sizes: [ [160,600] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + ] + } + adapter.callBids(params) + + expect(spy.calledWith('onBidResponse')).to.be.ok + expect(spy.calledWith('onResourceComplete')).to.be.ok + }) + }) + + describe('bids received', () => { + + beforeEach(() => { + sinon.spy(bidmanager, 'addBidResponse') + }) + + afterEach(() => { + bidmanager.addBidResponse.restore() + }) + + it('should call bidManager.addBidResponse() when bid received', () => { + + var options = { + bubbles: false, + cancelable: false, + detail: { + gross_price: 1000, + resource: { + position: 'atf', + pub_zone: 'testpub.com/combined', + size: '300x250' + } + } + } + + adapter.callBids({ + bidderCode: "pubgears", + bids: [ + { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + }, + { + bidder: "pubgears", + sizes: [ [160,600] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + ] + + }) + var script = document.getElementById('pg-header-tag') + var event = new CustomEvent('onBidResponse', options) + script.dispatchEvent(event) + + expect(bidmanager.addBidResponse.calledOnce).to.be.ok + }) + + it('should send correct bid response object when receiving onBidResponse event', () => { + expect(bidmanager.addBidResponse.calledOnce).to.not.be.ok + var bid = { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + + adapter.callBids({ + bidderCode: "pubgears", + bids: [ bid ] + }) + + var options = { + bubbles: false, + cancelable: false, + detail: { + gross_price: 1000, + resource: { + position: 'atf', + pub_zone: 'testpub.com/combined', + size: '300x250' + } + } + } + var script = document.getElementById('pg-header-tag') + var event = new CustomEvent('onBidResponse', options) + script.dispatchEvent(event) + + var args = bidmanager.addBidResponse.getCall(1).args + expect(args).to.have.length(2) + var bidResponse = args[1] + expect(bidResponse.ad).to.contain(bid.params.pubZone) + }) + + it('should send $0 bid as no-bid response', () => { + + var bid = { + bidder: "pubgears", + sizes: [ [300,250] ], + adUnitCode: "foo123/header-bid-tag", + params: { + publisherName: "integration", + pubZone: "testpub.com/combined" + } + } + + adapter.callBids({ + bidderCode: "pubgears", + bids: [ bid ] + }) + + var options = { + bubbles: false, + cancelable: false, + detail: { + gross_price: 0, + resource: { + position: 'atf', + pub_zone: 'testpub.com/combined', + size: '300x250' + } + } + } + var script = document.getElementById('pg-header-tag') + var event = new CustomEvent('onBidResponse', options) + + bidmanager.addBidResponse.reset() + script.dispatchEvent(event) + + var args = bidmanager.addBidResponse.getCall(1).args + var bidResponse = args[1] + expect(bidResponse).to.be.a('object') + expect(bidResponse.getStatusCode()).to.equal(2) + }) + }) +}) diff --git a/test/spec/adapters/quantcast_spec.js b/test/spec/adapters/quantcast_spec.js new file mode 100644 index 00000000000..ceca035d671 --- /dev/null +++ b/test/spec/adapters/quantcast_spec.js @@ -0,0 +1,194 @@ +import {expect} from 'chai'; +import Adapter from '../../../src/adapters/quantcast'; +import * as ajax from 'src/ajax'; +import bidManager from '../../../src/bidmanager'; +import adLoader from '../../../src/adloader'; + +describe('quantcast adapter', () => { + + let bidsRequestedOriginal; + let adapter; + let sandbox; + let ajaxStub; + + const bidderRequest = { + bidderCode: 'quantcast', + requestId : "595ffa73-d78a-46c9-b18e-f99548a5be6b", + bidderRequestId:"1cc026909c24c8", + bids: [ + { + bidId: '2f7b179d443f14', + bidder: 'quantcast', + placementCode: 'div-gpt-ad-1438287399331-0', + sizes: [[300,250],[300,600]], + params: { + publisherId: 'test-publisher', + battr : [1,2], + } + } + ] + }; + + beforeEach(() => { + bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; + $$PREBID_GLOBAL$$._bidsRequested = []; + + adapter = new Adapter(); + sandbox = sinon.sandbox.create(); + ajaxStub = sandbox.stub(ajax, 'ajax'); + }); + + afterEach(() => { + sandbox.restore(); + + $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; + }); + + describe('sizes', () => { + let bidderRequest = { + bidderCode: 'quantcast', + requestId : "595ffa73-d78a-46c9-b18e-f99548a5be6b", + bidderRequestId:"1cc026909c24c8", + bids: [ + { + bidId: '2f7b179d443f14', + bidder: 'quantcast', + placementCode: 'div-gpt-ad-1438287399331-0', + sizes: [[300,250],[300,600]], + params: { + publisherId: 'test-publisher', + battr : [1,2], + } + } + ] + }; + + it('should not call server when empty input is provided', () => { + adapter.callBids({}); + sinon.assert.notCalled(ajaxStub); + }); + + it('should call server once even when multiple sizes are passed', () => { + adapter.callBids(bidderRequest); + sinon.assert.calledOnce(ajaxStub); + + expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); + expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); + expect(ajaxStub.firstCall.args[2]).to.include('div-gpt-ad-1438287399331-0'); + expect(ajaxStub.firstCall.args[2]).to.include('test-publisher'); + expect(ajaxStub.firstCall.args[2]).to.include('2f7b179d443f14'); + expect(ajaxStub.firstCall.args[3]).to.eql({method : 'POST', withCredentials: true}); + }); + + it('should call server once when one size is passed', () => { + bidderRequest.bids[0].sizes = [728, 90]; + adapter.callBids(bidderRequest); + sinon.assert.calledOnce(ajaxStub); + + expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); + expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); + expect(ajaxStub.firstCall.args[3]).to.eql({method : 'POST', withCredentials: true}); + }); + + it('should call server once when size is passed as string', () => { + bidderRequest.bids[0].sizes = "728x90"; + adapter.callBids(bidderRequest); + sinon.assert.calledOnce(ajaxStub); + + expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); + expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); + expect(ajaxStub.firstCall.args[3]).to.eql({method : 'POST', withCredentials: true}); + }); + + it('should call server once when sizes are passed as a comma-separated string', () => { + bidderRequest.bids[0].sizes = "728x90,360x240"; + adapter.callBids(bidderRequest); + sinon.assert.calledOnce(ajaxStub); + + expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); + expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); + expect(ajaxStub.firstCall.args[3]).to.eql({method : 'POST', withCredentials: true}); + }); + + + }); + + describe('handleQuantcastCB add bids to the manager', () => { + + let firstBid; + let addBidReponseStub; + let bidsRequestedOriginal; + // respond + let bidderReponse = { + "bidderCode": "quantcast", + "requestId" : bidderRequest.requestId, + "bids" : [ + { + "statusCode" : 1, + "placementCode" : bidderRequest.bids[0].bidId, + "cpm": 4.5, + "ad": "\n\n\n
\n
\n\n \n\n\"Quantcast\"/\n\n
\n
", + "width": 300, + "height": 250 + } + ] + }; + + beforeEach(() => { + bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; + addBidReponseStub = sandbox.stub(bidManager, 'addBidResponse'); + $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + }); + + afterEach(() => { + sandbox.restore(); + $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; + }); + + it('should exist and be a function', () => { + expect($$PREBID_GLOBAL$$.handleQuantcastCB).to.exist.and.to.be.a('function'); + }); + + it('should not add bid when empty text response comes', () => { + $$PREBID_GLOBAL$$.handleQuantcastCB(); + sinon.assert.notCalled(addBidReponseStub); + }); + + it('should not add bid when empty json response comes', () => { + $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify({})); + sinon.assert.notCalled(addBidReponseStub); + }); + + it('should not add bid when malformed json response comes', () => { + $$PREBID_GLOBAL$$.handleQuantcastCB('non json text'); + sinon.assert.notCalled(addBidReponseStub); + }); + + it('should add a bid object for each bid', () => { + // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. + adapter.callBids(bidderRequest); + $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); + sinon.assert.calledOnce(addBidReponseStub); + expect(addBidReponseStub.firstCall.args[0]).to.eql("div-gpt-ad-1438287399331-0"); + }); + + it('should return no bid even when requestId and sizes are missing', () =>{ + let bidderReponse = { + "bidderCode": "quantcast", + "bids" : [ + { + "statusCode" : 0, + "placementCode" : bidderRequest.bids[0].bidId, + } + ] + }; + + // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. + adapter.callBids(bidderRequest); + $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); + //sinon.assert.calledOnce(addBidReponseStub); + //expect(addBidReponseStub.firstCall.args[0]).to.eql("div-gpt-ad-1438287399331-0"); + }); + }); + +}); diff --git a/test/spec/adapters/sharethrough_spec.js b/test/spec/adapters/sharethrough_spec.js index 7ede215d9aa..24884b87ffb 100644 --- a/test/spec/adapters/sharethrough_spec.js +++ b/test/spec/adapters/sharethrough_spec.js @@ -73,16 +73,18 @@ describe('sharethrough adapter', () => { let firstBid; let secondBid; + let server; beforeEach(() => { sandbox.stub(bidManager, 'addBidResponse'); + server = sinon.fakeServer.create(); pbjs._bidsRequested.push(bidderRequest); adapter.str.placementCodeSet['foo'] = {}; adapter.str.placementCodeSet['bar'] = {}; // respond - let bidderReponse1 = { + let bidderResponse1 = { "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994", "bidId": "bidId1", "creatives": [ @@ -95,7 +97,7 @@ describe('sharethrough adapter', () => { "stxUserId": "" }; - let bidderReponse2 = { + let bidderResponse2 = { "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994", "bidId": "bidId2", "creatives": [ @@ -108,13 +110,20 @@ describe('sharethrough adapter', () => { "stxUserId": "" }; - adapter.callback(bidderRequest.bids[0], JSON.stringify(bidderReponse1)); - adapter.callback(bidderRequest.bids[1], JSON.stringify(bidderReponse2)); + server.respondWith(/aaaa1111/,JSON.stringify(bidderResponse1)); + server.respondWith(/bbbb2222/,JSON.stringify(bidderResponse2)); + adapter.callBids(bidderRequest); + + server.respond(); firstBid = bidManager.addBidResponse.firstCall.args[1]; secondBid = bidManager.addBidResponse.secondCall.args[1]; }); + afterEach(() => { + server.restore(); + }); + it('should add a bid object for each bid', () => { sinon.assert.calledTwice(bidManager.addBidResponse); }); diff --git a/test/spec/adapters/sonobi_spec.js b/test/spec/adapters/sonobi_spec.js index 9a8c3f0a11a..ca0cd6735ee 100644 --- a/test/spec/adapters/sonobi_spec.js +++ b/test/spec/adapters/sonobi_spec.js @@ -60,6 +60,19 @@ describe('Sonobi adapter tests', () => { } }] }; + + const adUnit_as = { + code: 'sbi_s', + sizes: [[120, 600], [300, 600], [160, 600]], + bids: [{ + bidder: 'sonobi', + params: { + ad_unit: '/7780971/sparks_prebid_LB', + sizes: [[300, 250], [300, 600]] + } + }] + }; + const adUnit_ad = { bidderCode: 'sonobi', bids: [{ @@ -227,6 +240,7 @@ describe('Sonobi adapter tests', () => { 'adUnit_pd' : adUnit_pd, 'adUnit_pdf' : adUnit_pdf, 'adUnit_a' : adUnit_a, + 'adUnit_as' : adUnit_as, 'adUnit_ad' : adUnit_ad, 'adUnit_af' : adUnit_af, 'adUnit_adf' : adUnit_adf, diff --git a/test/spec/adapters/yieldbot_spec.js b/test/spec/adapters/yieldbot_spec.js new file mode 100644 index 00000000000..19de14380df --- /dev/null +++ b/test/spec/adapters/yieldbot_spec.js @@ -0,0 +1,220 @@ +import {expect} from 'chai'; +import YieldbotAdapter from 'src/adapters/yieldbot'; +import bidManager from 'src/bidmanager'; +import adLoader from 'src/adloader'; + +const bidderRequest = { + bidderCode: 'yieldbot', + bidder: 'yieldbot', + bidderRequestId: '187a340cb9ccc5', + bids: [ + { + bidId: '2640ad280208cc', + sizes: [[300, 250], [300, 600]], + bidder: 'yieldbot', + bidderRequestId: '187a340cb9ccc0', + params: { psn: '1234', slot: 'medrec' }, + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ece', + placementCode: '/4294967296/adunit0' + }, + { + bidId: '35751f10be5b6b', + sizes: [[728, 90], [970, 90]], + bidder: 'yieldbot', + bidderRequestId: '187a340cb9ccc1', + params: { psn: '1234', slot: 'leaderboard' }, + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ece', + placementCode: '/4294967296/adunit1' + } + ] +}; + +const YB_BID_FIXTURE = { + medrec: { + ybot_ad: 'y', + ybot_slot: 'medrec', + ybot_cpm: '200', + ybot_size: '300x250' + }, + leaderboard: { + ybot_ad: 'n' + } +}; + +function createYieldbotMockLib() { + // jshint unused:false + window.yieldbot = { + _initialized: false, + pub: (psn) => {}, + defineSlot: (slotName, optionalDomIdOrConfigObject, optionalTime) => {}, + enableAsync: () => {}, + go: () => { window.yieldbot._initialized = true; }, + nextPageview: (slots, callback) => {}, + getSlotCriteria: (slotName) => { + return YB_BID_FIXTURE[slotName] || {ybot_ad: "n"}; + } + }; + // jshint unused:true +} + +function restoreYieldbotMockLib() { + window.yieldbot = null; +} + +function mockYieldbotInitBidRequest() { + window.ybotq = window.ybotq || []; + window.ybotq.forEach(fn => { + fn.apply(window.yieldbot); + }); + window.ybotq = []; +} + +let sandbox; +let bidManagerStub; +let yieldbotLibStub; + +before(function() { + window.pbjs._bidsRequested.push(bidderRequest); +}); + +describe('Yieldbot adapter tests', function() { + + describe('callBids', function() { + beforeEach(function () { + + sandbox = sinon.sandbox.create(); + + createYieldbotMockLib(); + + sandbox.stub(adLoader, 'loadScript'); + yieldbotLibStub = sandbox.stub(window.yieldbot); + yieldbotLibStub.getSlotCriteria.restore(); + + bidManagerStub = sandbox.stub(bidManager, 'addBidResponse'); + + const adapter = new YieldbotAdapter(); + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + }); + + afterEach(function() { + sandbox.restore(); + restoreYieldbotMockLib(); + }); + + it('should request the yieldbot library', function() { + sinon.assert.calledOnce(adLoader.loadScript); + sinon.assert.calledWith(adLoader.loadScript, '//cdn.yldbt.com/js/yieldbot.intent.js'); + }); + + it('should set a yieldbot psn', function() { + sinon.assert.called(yieldbotLibStub.pub); + sinon.assert.calledWith(yieldbotLibStub.pub, '1234'); + }); + + it('should define yieldbot slots', function() { + sinon.assert.calledTwice(yieldbotLibStub.defineSlot); + sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [[300, 250], [300, 600]]}); + sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard', {sizes: [[728, 90], [970, 90]]}); + }); + + it('should enable yieldbot async mode', function() { + sinon.assert.called(yieldbotLibStub.enableAsync); + }); + + it('should add bid response after yieldbot request callback', function() { + const plc1 = bidManagerStub.firstCall.args[0]; + expect(plc1).to.equal(bidderRequest.bids[0].placementCode); + + const pb_bid1 = bidManagerStub.firstCall.args[1]; + expect(pb_bid1.bidderCode).to.equal('yieldbot'); + expect(pb_bid1.cpm).to.equal(2); + expect(pb_bid1.ybot_ad).to.equal('y'); + expect(pb_bid1.ybot_slot).to.equal('medrec'); + expect(pb_bid1.ybot_cpm).to.equal('200'); + expect(pb_bid1.ybot_size).to.equal('300x250'); + + expect(pb_bid1.width).to.equal('300'); + expect(pb_bid1.height).to.equal('250'); + expect(pb_bid1.ad).to.match(/src="\/\/cdn\.yldbt\.com\/js\/yieldbot\.intent\.js/); + expect(pb_bid1.ad).to.match(/yieldbot\.renderAd\('medrec:300x250'\)/); + + const plc2 = bidManagerStub.secondCall.args[0]; + expect(plc2).to.equal(bidderRequest.bids[1].placementCode); + + const pb_bid2 = bidManagerStub.secondCall.args[1]; + expect(pb_bid2.bidderCode).to.equal('yieldbot'); + expect(pb_bid2.width).to.equal(0); + expect(pb_bid2.height).to.equal(0); + expect(pb_bid2.statusMessage).to.match(/empty.*response/); + }); + }); + + describe('callBids, refresh', function() { + beforeEach(function () { + if (sandbox) { sandbox.restore(); } + sandbox = sinon.sandbox.create(); + + createYieldbotMockLib(); + + sandbox.stub(adLoader, 'loadScript'); + yieldbotLibStub = sandbox.stub(window.yieldbot); + yieldbotLibStub.getSlotCriteria.restore(); + yieldbotLibStub.go.restore(); + bidManagerStub = sandbox.stub(bidManager, 'addBidResponse'); + }); + + afterEach(function() { + sandbox.restore(); + restoreYieldbotMockLib(); + }); + + it('should use yieldbot.nextPageview after first callBids', function() { + const adapter = new YieldbotAdapter(); + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + + expect(window.yieldbot._initialized).to.equal(true); + + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + }); + + it('should not throw on callBids without bidsRequested', function() { + const adapter = new YieldbotAdapter(); + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + + expect(window.yieldbot._initialized).to.equal(true); + + window.pbjs._bidsRequested = window.pbjs._bidsRequested.filter(o => { + return o.bidderCode !== 'yieldbot'; + }); + + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + }); + + it('should not add empty bidResponse on callBids without bidsRequested', function() { + window.pbjs._bidsRequested = window.pbjs._bidsRequested.filter(o => { + return o.bidderCode !== 'yieldbot'; + }); + + const adapter = new YieldbotAdapter(); + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + + let bidResponses = window.pbjs._bidsReceived.filter(o => { + return o.bidderCode === 'yieldbot'; + }); + + expect(bidResponses.length).to.equal(0); + + adapter.callBids(bidderRequest); + mockYieldbotInitBidRequest(); + sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + }); + }); +}); diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index 49513251f60..951631d7eac 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -1,40 +1,4 @@ describe('adLoader', function () { var assert = require('chai').assert, adLoader = require('../../src/adloader'); - - describe('trackPixel', function () { - it('correctly appends a cachebuster query paramter to a pixel with no existing parameters', function () { - var inputUrl = 'http://www.example.com/tracking_pixel', - token = '?rnd=', - expectedPartialUrl = inputUrl + token, - actual = adLoader.trackPixel(inputUrl), - actualPartialUrl = actual.split(token)[0] + token, - randomNumber = parseInt(actual.split(token)[1]); - assert.strictEqual(actualPartialUrl, expectedPartialUrl); - assert.isNumber(randomNumber); - }); - }); - - it('correctly appends a cachebuster query paramter to a pixel with one existing parameter', function () { - var inputUrl = 'http://www.example.com/tracking_pixel?food=bard', - token = '&rnd=', - expectedPartialUrl = inputUrl + token, - actual = adLoader.trackPixel(inputUrl), - actualPartialUrl = actual.split(token)[0] + token, - randomNumber = parseInt(actual.split(token)[1]); - assert.strictEqual(actualPartialUrl, expectedPartialUrl); - assert.isNumber(randomNumber); - }); - - it('correctly appends a cachebuster query paramter to a pixel with multiple existing parameters', function () { - var inputUrl = 'http://www.example.com/tracking_pixel?food=bard&zing=zang', - token = '&rnd=', - expectedPartialUrl = inputUrl + token, - actual = adLoader.trackPixel(inputUrl), - actualPartialUrl = actual.split(token)[0] + token, - randomNumber = parseInt(actual.split(token)[1]); - assert.strictEqual(actualPartialUrl, expectedPartialUrl); - assert.isNumber(randomNumber); - }); - }); diff --git a/test/spec/cookie_spec.js b/test/spec/cookie_spec.js new file mode 100644 index 00000000000..1cf3fa80b02 --- /dev/null +++ b/test/spec/cookie_spec.js @@ -0,0 +1,33 @@ +import cookie from '../../src/cookie'; +import { expect } from 'chai'; +var utils = require('../../src/utils'); + +describe('cookie.queueSync', () => { + + let insertCookieSyncIframeStub = sinon.stub(utils, 'insertCookieSyncIframe'); + let insertPixelStub = sinon.stub(utils, 'insertPixel'); + + beforeEach(() => { + insertCookieSyncIframeStub.reset(); + insertPixelStub.reset(); + }); + + it('queues and fires a pixel URL', () => { + cookie.queueSync({'bidder' : 'testBidder', 'url': 'http://url.com'}); + cookie.syncCookies(); + expect(insertPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://url.com'); + }); + + it('queues and fires a iframe URL', () => { + cookie.queueSync({'url': 'http://url.com', 'type': 'iframe'}); + cookie.syncCookies(); + expect(insertCookieSyncIframeStub.getCall(0).args[0]).to.exist.and.to.equal('http://url.com'); + }); + + it('clears queue after sync', () => { + cookie.syncCookies(); + expect(insertCookieSyncIframeStub.callCount).to.equal(0); + expect(insertPixelStub.callCount).to.equal(0); + }); + +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index da32faca2d1..bae3bc89ea3 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1672,4 +1672,42 @@ describe('Unit: Prebid Module', function () { }); }); + describe('setS2SConfig', () => { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should log error when accountId is missing', () => { + const options = { + enabled : true, + bidders : ['appnexus'], + timeout : 1000, + adapter : 'prebidServer', + endpoint : 'https://prebid.adnxs.com/pbs/v1/auction' + }; + + $$PREBID_GLOBAL$$.setS2SConfig(options); + assert.ok(logErrorSpy.calledOnce, true); + }); + + it('should log error when bidders is missing', () => { + const options = { + accountId : '1', + enabled : true, + timeout : 1000, + adapter : 's2s', + endpoint : 'https://prebid.adnxs.com/pbs/v1/auction' + }; + + $$PREBID_GLOBAL$$.setS2SConfig(options); + assert.ok(logErrorSpy.calledOnce, true); + }); + }); + }); diff --git a/yarn.lock b/yarn.lock index db12fee1323..a64bddaef4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4747,7 +4747,7 @@ lodash@^3.0.1, lodash@^3.10.0, lodash@^3.10.1, lodash@^3.3.1, lodash@^3.5.0, lod version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.14.0, lodash@^4.16.2, lodash@^4.2.0: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.16.2, lodash@^4.17.4, lodash@^4.2.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"