diff --git a/integrationExamples/gpt/gpt_aliasingBidder.html b/integrationExamples/gpt/gpt_aliasingBidder.html index f9c7120079d..693be76e82e 100644 --- a/integrationExamples/gpt/gpt_aliasingBidder.html +++ b/integrationExamples/gpt/gpt_aliasingBidder.html @@ -35,7 +35,7 @@ // Load the Prebid Javascript Library Async. We recommend loading it immediately after // the initAdserver() and setTimeout functions. (function () { - var d = document, pbs = d.createElement("script"), pro = d.location.protocal; + var d = document, pbs = d.createElement("script"), pro = d.location.protocol; pbs.type = "text/javascript"; pbs.src = '/build/dist/prebid.js'; var target = document.getElementsByTagName("head")[0]; diff --git a/integrationExamples/gpt/pbjs_yieldbot_gpt.html b/integrationExamples/gpt/pbjs_yieldbot_gpt.html new file mode 100644 index 00000000000..986eed8fc5e --- /dev/null +++ b/integrationExamples/gpt/pbjs_yieldbot_gpt.html @@ -0,0 +1,201 @@ + + + + + + + + +

Prebid.js Yieldbot Adapter Basic Example

+ Use the links below to enable and disable Yieldbot test bids.
+
+ Note: +
+ The "Enable - Yieldbot Test Bids" link below will set a cookie to force Yieldbot bid requests to return static test creative: the cookie expires in 24 hrs. +
+ +
    +
  1. Enable - Yieldbot Test Bids
  2. +
  3. Disable - Yieldbot Test Bids
  4. +
+
Div-0, 728x90
+ +
+ +
+
Div-1, 300x250 or 300x600
+ +
+ +
+
Div-2, 300x250 or 300x600
+ The bid for the 300x250 | 300x600 slot is shown under Div-1 above. + + +
+ +
+ + diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 5d1bb98b4ec..69908e70888 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -1,14 +1,15 @@ -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); +var utils = require('src/utils'); +var adloader = require('src/adloader'); +var bidmanager = require('src/bidmanager'); +var bidfactory = require('src/bidfactory'); var STATUSCODES = require('src/constants.json').STATUS; var adaptermanager = require('src/adaptermanager'); +var Adapter = require('src/adapter').default; + +const ADFORM_BIDDER_CODE = 'adform'; function AdformAdapter() { - return { - callBids: _callBids - }; + let baseAdapter = new Adapter(ADFORM_BIDDER_CODE); function _callBids(params) { var bid, _value, _key, i, j, k, l, reqParams; @@ -47,7 +48,7 @@ function AdformAdapter() { $$PREBID_GLOBAL$$[callbackName] = handleCallback(bids); adloader.loadScript(request.join('&')); - } + }; function formRequestUrl(reqData) { var key; @@ -63,7 +64,7 @@ function AdformAdapter() { function handleCallback(bids) { return function handleResponse(adItems) { var bidObject; - var bidder = 'adform'; + var bidder = baseAdapter.getBidderCode(); var adItem; var bid; for (var i = 0, l = adItems.length; i < l; i++) { @@ -160,8 +161,11 @@ function AdformAdapter() { return utftext; } -} -adaptermanager.registerBidAdapter(new AdformAdapter(), 'adform'); + return Object.assign(this, baseAdapter, { + callBids: _callBids + }); +} +adaptermanager.registerBidAdapter(new AdformAdapter(), ADFORM_BIDDER_CODE); module.exports = AdformAdapter; diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js new file mode 100644 index 00000000000..ccb0287a866 --- /dev/null +++ b/modules/adxcgBidAdapter.js @@ -0,0 +1,139 @@ +import bidfactory from 'src/bidfactory'; +import bidmanager from 'src/bidmanager'; +import * as utils from 'src/utils'; +import { STATUS } from 'src/constants'; +import adaptermanager from 'src/adaptermanager'; +import { ajax } from 'src/ajax'; +import * as url from 'src/url'; + +/** + * Adapter for requesting bids from Adxcg + * updated from latest prebid repo on 2017.08.30 + */ +function AdxcgAdapter() { + let bidRequests = {}; + + function _callBids(params) { + if (params.bids && params.bids.length > 0) { + let adZoneIds = []; + let prebidBidIds = []; + let sizes = []; + + params.bids.forEach(bid => { + bidRequests[bid.bidId] = bid; + adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); + prebidBidIds.push(bid.bidId); + sizes.push(utils.parseSizesInput(bid.sizes).join('|')); + }); + + let location = utils.getTopWindowLocation(); + let secure = location.protocol == 'https:'; + + let requestUrl = url.parse(location.href); + requestUrl.search = null; + requestUrl.hash = null; + + let adxcgRequestUrl = url.format({ + protocol: secure ? 'https' : 'http', + hostname: secure ? 'ad-emea-secure.adxcg.net' : 'ad-emea.adxcg.net', + pathname: '/get/adi', + search: { + renderformat: 'javascript', + ver: 'r20141124', + adzoneid: adZoneIds.join(','), + format: sizes.join(','), + prebidBidIds: prebidBidIds.join(','), + url: escape(url.format(requestUrl)), + secure: secure ? '1' : '0' + } + }); + + utils.logMessage(`submitting request: ${adxcgRequestUrl}`); + ajax(adxcgRequestUrl, handleResponse, null, { + withCredentials: true + }); + } + } + + function handleResponse(response) { + let adxcgBidReponseList; + + try { + adxcgBidReponseList = JSON.parse(response); + utils.logMessage(`adxcgBidReponseList: ${JSON.stringify(adxcgBidReponseList)}`); + } catch (error) { + adxcgBidReponseList = []; + utils.logError(error); + } + + adxcgBidReponseList.forEach(adxcgBidReponse => { + let bidRequest = bidRequests[adxcgBidReponse.bidId]; + delete bidRequests[adxcgBidReponse.bidId]; + + let bid = bidfactory.createBid(STATUS.GOOD, bidRequest); + + bid.creative_id = adxcgBidReponse.creativeId; + bid.code = 'adxcg'; + bid.bidderCode = 'adxcg'; + bid.cpm = adxcgBidReponse.cpm; + + if (adxcgBidReponse.ad) { + bid.ad = adxcgBidReponse.ad; + } else if (adxcgBidReponse.vastUrl) { + bid.vastUrl = adxcgBidReponse.vastUrl; + bid.descriptionUrl = adxcgBidReponse.vastUrl; + bid.mediaType = 'video'; + } else if (adxcgBidReponse.nativeResponse) { + bid.mediaType = 'native'; + + let nativeResponse = adxcgBidReponse.nativeResponse; + + bid.native = { + clickUrl: escape(nativeResponse.link.url), + impressionTrackers: nativeResponse.imptrackers + }; + + nativeResponse.assets.forEach(asset => { + if (asset.title && asset.title.text) { + bid.native.title = asset.title.text; + } + + if (asset.img && asset.img.url) { + bid.native.image = asset.img.url; + } + + if (asset.data && asset.data.label == 'DESC' && asset.data.value) { + bid.native.body = asset.data.value; + } + + if (asset.data && asset.data.label == 'SPONSORED' && asset.data.value) { + bid.native.sponsoredBy = asset.data.value; + } + }); + } + + bid.width = adxcgBidReponse.width; + bid.height = adxcgBidReponse.height; + + utils.logMessage(`submitting bid[${bidRequest.placementCode}]: ${JSON.stringify(bid)}`); + bidmanager.addBidResponse(bidRequest.placementCode, bid); + }); + + Object.keys(bidRequests) + .map(bidId => bidRequests[bidId].placementCode) + .forEach(placementCode => { + utils.logMessage(`creating no_bid bid for: ${placementCode}`); + bidmanager.addBidResponse(placementCode, bidfactory.createBid(STATUS.NO_BID)); + }); + }; + + return { + callBids: _callBids + }; +}; + +adaptermanager.registerBidAdapter(new AdxcgAdapter(), 'adxcg', { + supportedMediaTypes: ['video', 'native'] +}); + +module.exports = AdxcgAdapter; diff --git a/modules/aerservBidAdapter.js b/modules/aerservBidAdapter.js new file mode 100644 index 00000000000..64a02e77325 --- /dev/null +++ b/modules/aerservBidAdapter.js @@ -0,0 +1,117 @@ +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 adaptermanager from 'src/adaptermanager'; + +const BIDDER_CODE = 'aerserv'; + +const AerServAdapter = function AerServAdapter() { + const ENVIRONMENTS = { + local: '127.0.0.1:8080', + dev: 'dev-ads.aerserv.com', + stage: 'staging-ads.aerserv.com', + prod: 'ads.aerserv.com' + }; + + const BANNER_PATH = '/as/json/pbjs/v1?'; + const VIDEO_PATH = '/as/json/pbjsvast/v1?'; + const REQUIRED_PARAMS = ['plc']; + + function _isResponseValid(bidRequest, response) { + return ((bidRequest.mediaType === 'video' && response.vastUrl) || (bidRequest.mediaType !== 'video' && response.adm)) && + response.cpm && response.cpm > 0; + } + + function _createBid(bidRequest, response) { + if (_isResponseValid(bidRequest, response)) { + let bid = bidfactory.createBid(1, bidRequest); + bid.bidderCode = BIDDER_CODE; + bid.cpm = response.cpm; + bid.width = response.w; + bid.height = response.h; + if (bidRequest.mediaType === 'video') { + bid.vastUrl = response.vastUrl; + bid.descriptionUrl = response.vastUrl; + bid.mediaType = 'video'; + } else { + bid.ad = response.adm; + } + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } else { + bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest)); + } + } + + function _getFirstSize(sizes) { + let sizeObj = {}; + if (utils.isArray(sizes) && sizes.length > 0 && utils.isArray(sizes[0]) && sizes[0].length === 2) { + sizeObj['vpw'] = sizes[0][0]; + sizeObj['vph'] = sizes[0][1]; + } + return sizeObj; + } + + function _buildQueryParameters(bid, requestParams) { + Object.keys(bid.params).filter(param => param !== 'video') + .forEach(param => requestParams[param] = bid.params[param]); + + if (bid.mediaType === 'video') { + let videoDimensions = _getFirstSize(bid.sizes); + Object.keys(videoDimensions).forEach(param => requestParams[param] = videoDimensions[param]); + Object.keys(bid.params.video || {}).forEach(param => requestParams[param] = bid.params.video[param]); + } + + return utils.parseQueryStringParameters(requestParams); + } + + function _handleResponse(bidRequest) { + return response => { + if (!response && response.length <= 0) { + bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest)); + utils.logError('Empty response'); + return; + } + + try { + response = JSON.parse(response); + } catch (e) { + bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest)); + utils.logError('Invalid JSON in response'); + return; + } + + _createBid(bidRequest, response); + }; + } + + function _callBids(bidRequests) { + let currentUrl = (window.parent !== window) ? document.referrer : window.location.href; + currentUrl = currentUrl && encodeURIComponent(currentUrl); + + let bids = bidRequests.bids || []; + bids.forEach(bid => { + if (utils.hasValidBidRequest(bid.params, REQUIRED_PARAMS, BIDDER_CODE)) { + let env = ENVIRONMENTS[bid.params['env']] || ENVIRONMENTS['prod']; + let requestPath = bid.mediaType === 'video' ? VIDEO_PATH : BANNER_PATH; + let pageParameters = {url: currentUrl}; + let parameterStr = _buildQueryParameters(bid, pageParameters); + + let url = `//${env}${requestPath}${parameterStr}`; + utils.logMessage('sending request to: ' + url); + ajax(url, _handleResponse(bid), null, {withCredentials: true}); + } else { + bidmanager.addBidResponse(bid.placementCode, bidfactory.createBid(STATUS.NO_BID, bid)); + } + }); + } + + return { + callBids: _callBids + } +}; + +adaptermanager.registerBidAdapter(new AerServAdapter(), BIDDER_CODE, {supportedMediaTypes: ['video']}); + +module.exports = AerServAdapter; diff --git a/modules/appnexusAstBidAdapter.js b/modules/appnexusAstBidAdapter.js index 80e2472e9ef..5ca2178d0a4 100644 --- a/modules/appnexusAstBidAdapter.js +++ b/modules/appnexusAstBidAdapter.js @@ -25,6 +25,7 @@ const NATIVE_MAPPING = { }, sponsoredBy: 'sponsored_by' }; +const SOURCE = 'pbjs'; /** * Bidder adapter for /ut endpoint. Given the list of all ad unit tag IDs, @@ -142,7 +143,14 @@ function AppnexusAstAdapter() { }); if (!utils.isEmpty(tags)) { - const payloadJson = {tags: [...tags], user: userObj}; + const payloadJson = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + } + }; if (member > 0) { payloadJson.member_id = member; } diff --git a/modules/currency.js b/modules/currency.js new file mode 100644 index 00000000000..1d0286ed569 --- /dev/null +++ b/modules/currency.js @@ -0,0 +1,255 @@ +import bidfactory from 'src/bidfactory'; +import { STATUS } from 'src/constants'; +import { ajax } from 'src/ajax'; +import * as utils from 'src/utils'; +import bidmanager from 'src/bidmanager'; +import { config } from 'src/config'; + +const DEFAULT_CURRENCY_RATE_URL = 'http://currency.prebid.org/latest.json'; +const CURRENCY_RATE_PRECISION = 4; + +var bidResponseQueue = []; +var conversionCache = {}; +var currencyRatesLoaded = false; +var adServerCurrency = 'USD'; + +// Used as reference to the original bidmanager.addBidResponse +var originalBidResponse; + +export var currencySupportEnabled = false; +export var currencyRates = {}; +var bidderCurrencyDefault = {}; + +/** + * Configuration function for currency + * @param {string} [config.adServerCurrency = 'USD'] + * ISO 4217 3-letter currency code that represents the target currency. (e.g. 'EUR'). If this value is present, + * the currency conversion feature is activated. + * @param {number} [config.granularityMultiplier = 1] + * A decimal value representing how mcuh to scale the price granularity calculations. + * @param {object} config.bidderCurrencyDefault + * An optional argument to specify bid currencies for bid adapters. This option is provided for the transitional phase + * before every bid adapter will specify its own bid currency. If the adapter specifies a bid currency, this value is + * ignored for that bidder. + * + * example: + * { + * rubicon: 'USD' + * } + * @param {string} [config.conversionRateFile = 'http://currency.prebid.org/latest.json'] + * Optional path to a file containing currency conversion data. Prebid.org hosts a file that is used as the default, + * if not specified. + * @param {object} [config.rates] + * This optional argument allows you to specify the rates with a JSON object, subverting the need for a external + * config.conversionRateFile parameter. If this argument is specified, the conversion rate file will not be loaded. + * + * example: + * { + * 'GBP': { 'CNY': 8.8282, 'JPY': 141.7, 'USD': 1.2824 }, + * 'USD': { 'CNY': 6.8842, 'GBP': 0.7798, 'JPY': 110.49 } + * } + */ +export function setConfig(config) { + let url = DEFAULT_CURRENCY_RATE_URL; + + if (typeof config.rates === 'object') { + currencyRates.conversions = config.rates; + currencyRatesLoaded = true; + } + + if (typeof config.adServerCurrency === 'string') { + utils.logInfo('enabling currency support', arguments); + + adServerCurrency = config.adServerCurrency; + if (config.conversionRateFile) { + utils.logInfo('currency using override conversionRateFile:', config.conversionRateFile); + url = config.conversionRateFile; + } + initCurrency(url); + } else { + // currency support is disabled, setting defaults + utils.logInfo('disabling currency support'); + resetCurrency(); + } + if (typeof config.bidderCurrencyDefault === 'object') { + bidderCurrencyDefault = config.bidderCurrencyDefault; + } +} +config.getConfig('currency', config => setConfig(config.currency)); + +function initCurrency(url) { + conversionCache = {}; + currencySupportEnabled = true; + + if (!originalBidResponse) { + utils.logInfo('Installing addBidResponse decorator for currency module', arguments); + + originalBidResponse = bidmanager.addBidResponse; + bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse); + } + + if (!currencyRates.conversions) { + ajax(url, function (response) { + try { + currencyRates = JSON.parse(response); + utils.logInfo('currencyRates set to ' + JSON.stringify(currencyRates)); + currencyRatesLoaded = true; + processBidResponseQueue(); + } catch (e) { + utils.logError('failed to parse currencyRates response: ' + response); + } + }); + } +} + +function resetCurrency() { + if (originalBidResponse) { + utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); + + bidmanager.addBidResponse = originalBidResponse; + originalBidResponse = undefined; + } + + adServerCurrency = 'USD'; + conversionCache = {}; + currencySupportEnabled = false; + currencyRatesLoaded = false; + currencyRates = {}; + bidderCurrencyDefault = {}; +} + +export function addBidResponseDecorator(fn) { + return function(adUnitCode, bid) { + if (!bid) { + return fn.apply(this, arguments); // if no bid, call original and let it display warnings + } + + let bidder = bid.bidderCode || bid.bidder; + if (bidderCurrencyDefault[bidder]) { + let currencyDefault = bidderCurrencyDefault[bidder]; + if (bid.currency && currencyDefault !== bid.currency) { + utils.logWarn(`Currency default '${bidder}: ${currencyDefault}' ignored. adapter specified '${bid.currency}'`); + } else { + bid.currency = currencyDefault; + } + } + + // default to USD if currency not set + if (!bid.currency) { + utils.logWarn('Currency not specified on bid. Defaulted to "USD"'); + bid.currency = 'USD'; + } + + // execute immediately if the bid is already in the desired currency + if (bid.currency === adServerCurrency) { + return fn.apply(this, arguments); + } + + bidResponseQueue.push(wrapFunction(fn, this, arguments)); + if (!currencySupportEnabled || currencyRatesLoaded) { + processBidResponseQueue(); + } + } +} + +function processBidResponseQueue() { + while (bidResponseQueue.length > 0) { + (bidResponseQueue.shift())(); + } +} + +function wrapFunction(fn, context, params) { + return function() { + var bid = params[1]; + if (bid !== undefined && 'currency' in bid && 'cpm' in bid) { + var fromCurrency = bid.currency; + try { + var conversion = getCurrencyConversion(fromCurrency); + bid.originalCpm = bid.cpm; + bid.originalCurrency = bid.currency; + if (conversion !== 1) { + bid.cpm = (parseFloat(bid.cpm) * conversion).toFixed(4); + bid.currency = adServerCurrency; + } + } catch (e) { + utils.logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); + params[1] = bidfactory.createBid(STATUS.NO_BID, { + bidder: bid.bidderCode || bid.bidder, + bidId: bid.adId + }); + } + } + return fn.apply(context, params); + }; +} + +function getCurrencyConversion(fromCurrency) { + var conversionRate = null; + var rates; + + if (fromCurrency in conversionCache) { + conversionRate = conversionCache[fromCurrency]; + utils.logMessage('Using conversionCache value ' + conversionRate + ' for fromCurrency ' + fromCurrency); + } else if (currencySupportEnabled === false) { + if (fromCurrency === 'USD') { + conversionRate = 1; + } else { + throw new Error('Prebid currency support has not been enabled and fromCurrency is not USD'); + } + } else if (fromCurrency === adServerCurrency) { + conversionRate = 1; + } else { + var toCurrency = adServerCurrency; + + if (fromCurrency in currencyRates.conversions) { + // using direct conversion rate from fromCurrency to toCurrency + rates = currencyRates.conversions[fromCurrency]; + if (!(toCurrency in rates)) { + // bid should fail, currency is not supported + throw new Error('Specified adServerCurrency in config \'' + toCurrency + '\' not found in the currency rates file'); + } + conversionRate = rates[toCurrency]; + utils.logInfo('getCurrencyConversion using direct ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + } else if (toCurrency in currencyRates.conversions) { + // using reciprocal of conversion rate from toCurrency to fromCurrency + rates = currencyRates.conversions[toCurrency]; + if (!(fromCurrency in rates)) { + // bid should fail, currency is not supported + throw new Error('Specified fromCurrency \'' + fromCurrency + '\' not found in the currency rates file'); + } + conversionRate = roundFloat(1 / rates[fromCurrency], CURRENCY_RATE_PRECISION); + utils.logInfo('getCurrencyConversion using reciprocal ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + } else { + // first defined currency base used as intermediary + var anyBaseCurrency = Object.keys(currencyRates.conversions)[0]; + + if (!(fromCurrency in currencyRates.conversions[anyBaseCurrency])) { + // bid should fail, currency is not supported + throw new Error('Specified fromCurrency \'' + fromCurrency + '\' not found in the currency rates file'); + } + var toIntermediateConversionRate = 1 / currencyRates.conversions[anyBaseCurrency][fromCurrency]; + + if (!(toCurrency in currencyRates.conversions[anyBaseCurrency])) { + // bid should fail, currency is not supported + throw new Error('Specified adServerCurrency in config \'' + toCurrency + '\' not found in the currency rates file'); + } + var fromIntermediateConversionRate = currencyRates.conversions[anyBaseCurrency][toCurrency]; + + conversionRate = roundFloat(toIntermediateConversionRate * fromIntermediateConversionRate, CURRENCY_RATE_PRECISION); + utils.logInfo('getCurrencyConversion using intermediate ' + fromCurrency + ' thru ' + anyBaseCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate); + } + } + if (!(fromCurrency in conversionCache)) { + utils.logMessage('Adding conversionCache value ' + conversionRate + ' for fromCurrency ' + fromCurrency); + conversionCache[fromCurrency] = conversionRate; + } + return conversionRate; +} + +function roundFloat(num, dec) { + var d = 1; + for (let i = 0; i < dec; i++) { + d += '0'; + } + return Math.round(num * d) / d; +} diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js new file mode 100644 index 00000000000..51fb61b03e2 --- /dev/null +++ b/modules/improvedigitalBidAdapter.js @@ -0,0 +1,384 @@ +const LIB_VERSION_GLOBAL = '3.0.5'; + +const CONSTANTS = require('src/constants'); +const utils = require('src/utils'); +const bidfactory = require('src/bidfactory'); +const bidmanager = require('src/bidmanager'); +const adloader = require('src/adloader'); +const Adapter = require('src/adapter').default; +const adaptermanager = require('src/adaptermanager'); + +const IMPROVE_DIGITAL_BIDDER_CODE = 'improvedigital'; + +const ImproveDigitalAdapter = function () { + let baseAdapter = new Adapter(IMPROVE_DIGITAL_BIDDER_CODE); + baseAdapter.idClient = new ImproveDigitalAdServerJSClient('hb'); + + const LIB_VERSION = LIB_VERSION_GLOBAL; + + // Ad server needs to implement JSONP using this function as the callback + const CALLBACK_FUNCTION = '$$PREBID_GLOBAL$$' + '.improveDigitalResponse'; + + baseAdapter.getNormalizedBidRequest = function(bid) { + let adUnitId = utils.getBidIdParameter('placementCode', bid) || null; + let placementId = utils.getBidIdParameter('placementId', bid.params) || null; + let publisherId = null; + let placementKey = null; + + if (placementId === null) { + publisherId = utils.getBidIdParameter('publisherId', bid.params) || null; + placementKey = utils.getBidIdParameter('placementKey', bid.params) || null; + } + let keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; + let localSize = utils.getBidIdParameter('size', bid.params) || null; + let bidId = utils.getBidIdParameter('bidId', bid); + + let normalizedBidRequest = {}; + if (placementId) { + normalizedBidRequest.placementId = placementId; + } else { + if (publisherId) { + normalizedBidRequest.publisherId = publisherId; + } + if (placementKey) { + normalizedBidRequest.placementKey = placementKey; + } + } + + if (keyValues) { + normalizedBidRequest.keyValues = keyValues; + } + if (localSize && localSize.w && localSize.h) { + normalizedBidRequest.size = {}; + normalizedBidRequest.size.h = localSize.h; + normalizedBidRequest.size.w = localSize.w; + } + if (bidId) { + normalizedBidRequest.id = bidId; + } + if (adUnitId) { + normalizedBidRequest.adUnitId = adUnitId; + } + return normalizedBidRequest; + } + + let submitNoBidResponse = function(bidRequest) { + let bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequest); + bid.bidderCode = IMPROVE_DIGITAL_BIDDER_CODE; + bidmanager.addBidResponse(bidRequest.placementCode, bid); + }; + + $$PREBID_GLOBAL$$.improveDigitalResponse = function(response) { + let bidRequests = utils.getBidderRequestAllAdUnits(IMPROVE_DIGITAL_BIDDER_CODE); + if (bidRequests && bidRequests.bids && bidRequests.bids.length > 0) { + utils._each(bidRequests.bids, function (bidRequest) { + let bidObjects = response.bid || []; + utils._each(bidObjects, function (bidObject) { + if (bidObject.id === bidRequest.bidId) { + if (!bidObject.price || bidObject.price === null) { + submitNoBidResponse(bidRequest); + return; + } + if (bidObject.errorCode && bidObject.errorCode !== 0) { + submitNoBidResponse(bidRequest); + return; + } + if (!bidObject.adm || bidObject.adm === null || typeof bidObject.adm !== 'string') { + submitNoBidResponse(bidRequest); + return; + } + + let bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidRequest); + + let syncString = ''; + let syncArray = (bidObject.sync && bidObject.sync.length > 0) ? bidObject.sync : []; + + utils._each(syncArray, function (syncElement) { + let syncInd = syncElement.replace(/\//g, '\\\/'); + syncString = `${syncString}${(syncString === '') ? 'document.writeln(\"' : ''}`; + }); + syncString = `${syncString}${(syncString === '') ? '' : '\")'}`; + + let nurl = ''; + if (bidObject.nurl && bidObject.nurl.length > 0) { + nurl = ``; + } + bid.ad = `${nurl}`; + bid.bidderCode = IMPROVE_DIGITAL_BIDDER_CODE; + bid.cpm = parseFloat(bidObject.price); + bid.width = bidObject.w; + bid.height = bidObject.h; + + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } + }); + }); + } + }; + + baseAdapter.callBids = function (params) { + // params will contain an array + let bidRequests = params.bids || []; + let loc = utils.getTopWindowLocation(); + let requestParameters = { + singleRequestMode: false, + httpRequestType: this.idClient.CONSTANTS.HTTP_REQUEST_TYPE.GET, + callback: CALLBACK_FUNCTION, + secure: (loc.protocol === 'https:') ? 1 : 0, + libVersion: this.LIB_VERSION + }; + + let normalizedBids = bidRequests.map((bidRequest) => { + let normalizedBidRequest = this.getNormalizedBidRequest(bidRequest); + if (bidRequest.params && bidRequest.params.singleRequest) { + requestParameters.singleRequestMode = true; + } + return normalizedBidRequest; + }); + + let request = this.idClient.createRequest( + normalizedBids, // requestObject + requestParameters + ); + + if (request.errors && request.errors.length > 0) { + utils.logError('ID WARNING 0x01'); + } + + if (request && request.requests && request.requests[0]) { + utils._each(request.requests, function (requestElement) { + if (requestElement.url) { + adloader.loadScript(requestElement.url, null); + } + }); + } + } + + // Export the callBids function, so that prebid.js can execute this function + // when the page asks to send out bid requests. + return Object.assign(this, { + LIB_VERSION: LIB_VERSION, + idClient: baseAdapter.idClient, + getNormalizedBidRequest: baseAdapter.getNormalizedBidRequest, + callBids: baseAdapter.callBids + }); +}; + +ImproveDigitalAdapter.createNew = function () { + return new ImproveDigitalAdapter(); +}; + +adaptermanager.registerBidAdapter(new ImproveDigitalAdapter(), IMPROVE_DIGITAL_BIDDER_CODE); + +module.exports = ImproveDigitalAdapter; + +function ImproveDigitalAdServerJSClient(endPoint) { + this.CONSTANTS = { + HTTP_REQUEST_TYPE: { + GET: 0, + POST: 1 + }, + HTTP_SECURITY: { + STANDARD: 0, + SECURE: 1 + }, + AD_SERVER_BASE_URL: 'ad.360yield.com', + END_POINT: endPoint || 'hb', + AD_SERVER_URL_PARAM: '?jsonp=', + CLIENT_VERSION: 'JS-4.0.2', + MAX_URL_LENGTH: 2083, + ERROR_CODES: { + BAD_HTTP_REQUEST_TYPE_PARAM: 1, + MISSING_PLACEMENT_PARAMS: 2, + LIB_VERSION_MISSING: 3 + } + }; + + this.getErrorReturn = function(errorCode) { + return { + idMappings: {}, + requests: {}, + 'errorCode': errorCode + }; + }; + + this.createRequest = function(requestObject, requestParameters, extraRequestParameters) { + if (requestParameters.httpRequestType !== this.CONSTANTS.HTTP_REQUEST_TYPE.GET) { + return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.BAD_HTTP_REQUEST_TYPE_PARAM); + } + if (!requestParameters.libVersion) { + return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING); + } + + let impressionObjects = []; + let impressionObject; + let counter; + if (utils.isArray(requestObject)) { + for (counter = 0; counter < requestObject.length; counter++) { + impressionObject = this.createImpressionObject(requestObject[counter]); + impressionObjects.push(impressionObject); + } + } else { + impressionObject = this.createImpressionObject(requestObject); + impressionObjects.push(impressionObject); + } + + let returnObject = {}; + returnObject.idMappings = []; + returnObject.requests = []; + let errors = null; + + let baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + + let bidRequestObject = { + bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) + }; + for (counter = 0; counter < impressionObjects.length; counter++) { + impressionObject = impressionObjects[counter]; + + if (impressionObject.errorCode) { + errors = errors || []; + errors.push({ + errorCode: impressionObject.errorCode, + adUnitId: impressionObject.adUnitId + }); + } else { + returnObject.idMappings.push({ + adUnitId: impressionObject.adUnitId, + id: impressionObject.impressionObject.id + }); + bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; + + bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); + let outputUri = encodeURIComponent(baseUrl + JSON.stringify(bidRequestObject)); + + if (!requestParameters.singleRequestMode) { + returnObject.requests.push({ + url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + }); + bidRequestObject = { + bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) + }; + } + + if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) { + if (bidRequestObject.bid_request.imp.length > 1) { + bidRequestObject.bid_request.imp.pop(); + returnObject.requests.push({ + url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + }); + bidRequestObject = { + bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) + }; + bidRequestObject.bid_request.imp = []; + bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); + } else { + // We have a problem. Single request is too long for a URI + } + } + } + } + if (bidRequestObject.bid_request && + bidRequestObject.bid_request.imp && + bidRequestObject.bid_request.imp.length > 0) { + returnObject.requests = returnObject.requests || []; + returnObject.requests.push({ + url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + }); + } + + if (errors) { + returnObject.errors = errors; + } + + return returnObject; + }; + + this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { + let impressionBidRequestObject = {}; + if (requestParameters.requestId) { + impressionBidRequestObject.id = requestParameters.requestId; + } else { + impressionBidRequestObject.id = utils.getUniqueIdentifierStr(); + } + if (requestParameters.domain) { + impressionBidRequestObject.domain = requestParameters.domain; + } + if (requestParameters.page) { + impressionBidRequestObject.page = requestParameters.page; + } + if (requestParameters.ref) { + impressionBidRequestObject.ref = requestParameters.ref; + } + if (requestParameters.callback) { + impressionBidRequestObject.callback = requestParameters.callback; + } + if ('secure' in requestParameters) { + impressionBidRequestObject.secure = requestParameters.secure; + } + if (requestParameters.libVersion) { + impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION; + } + if (extraRequestParameters) { + for (let prop in extraRequestParameters) { + impressionBidRequestObject[prop] = extraRequestParameters[prop]; + } + } + + return impressionBidRequestObject; + }; + + this.createImpressionObject = function(placementObject) { + let outputObject = {}; + let impressionObject = {}; + outputObject.impressionObject = impressionObject; + + if (placementObject.id) { + impressionObject.id = placementObject.id; + } else { + impressionObject.id = utils.getUniqueIdentifierStr(); + } + if (placementObject.adUnitId) { + outputObject.adUnitId = placementObject.adUnitId; + } + if (placementObject.placementId) { + impressionObject.pid = placementObject.placementId; + } + if (placementObject.publisherId) { + impressionObject.pubid = placementObject.publisherId; + } + if (placementObject.placementKey) { + impressionObject.pkey = placementObject.placementKey; + } + if (placementObject.transactionId) { + impressionObject.tid = placementObject.transactionId; + } + if (placementObject.keyValues) { + for (let key in placementObject.keyValues) { + for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { + impressionObject.kvw = impressionObject.kvw || {}; + impressionObject.kvw[key] = impressionObject.kvw[key] || []; + impressionObject.kvw[key].push(placementObject.keyValues[key][valueCounter]); + } + } + } + if (placementObject.size && placementObject.size.w && placementObject.size.h) { + impressionObject.banner = {}; + impressionObject.banner.w = placementObject.size.w; + impressionObject.banner.h = placementObject.size.h; + } else { + impressionObject.banner = {}; + } + + if (!impressionObject.pid && + !impressionObject.pubid && + !impressionObject.pkey && + !(impressionObject.banner && impressionObject.banner.w && impressionObject.banner.h)) { + outputObject.impressionObject = null; + outputObject.errorCode = this.CONSTANTS.ERROR_CODES.MISSING_PLACEMENT_PARAMS; + } + return outputObject; + }; +} + +exports.ImproveDigitalAdServerJSClient = ImproveDigitalAdServerJSClient; diff --git a/modules/indexExchangeBidAdapter.js b/modules/indexExchangeBidAdapter.js index 3f1984f82df..352613b4887 100644 --- a/modules/indexExchangeBidAdapter.js +++ b/modules/indexExchangeBidAdapter.js @@ -269,11 +269,24 @@ var cygnus_index_start = function () { this.siteID = siteID; this.impressions = []; this._parseFnName = undefined; + + // Get page URL + this.sitePage = undefined; + try { + this.sitePage = utils.getTopWindowUrl(); + } catch (e) {} + // Fallback to old logic if utils.getTopWindowUrl() fails to return site.page + if (typeof this.sitePage === 'undefined' || this.sitePage === '') { + if (top === self) { + this.sitePage = location.href; + } else { + this.sitePage = document.referrer; + } + } + if (top === self) { - this.sitePage = location.href; this.topframe = 1; } else { - this.sitePage = document.referrer; this.topframe = 0; } diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index cd45d6fae40..d59a21c3643 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -441,7 +441,7 @@ function syncEmily(hasSynced) { } const defaultUserSyncConfig = { - enabled: true, + enabled: false, delay: 5000 }; const iframeUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; diff --git a/modules/serverbidBidAdapter.js b/modules/serverbidBidAdapter.js index 04f2e9964a9..7718e30e55f 100644 --- a/modules/serverbidBidAdapter.js +++ b/modules/serverbidBidAdapter.js @@ -8,9 +8,12 @@ import adaptermanager from 'src/adaptermanager'; const ServerBidAdapter = function ServerBidAdapter() { const baseAdapter = new Adapter('serverbid'); - const BASE_URI = '//e.serverbid.com/api/v2'; + const SERVERBID_BASE_URI = 'https://e.serverbid.com/api/v2'; + const SMARTSYNC_BASE_URI = 'https://s.zkcdn.net/ss'; + const SMARTSYNC_CALLBACK = 'serverbidCallBids'; - const sizeMap = [null, + const sizeMap = [ + null, '120x90', '120x90', '468x60', @@ -45,44 +48,67 @@ const ServerBidAdapter = function ServerBidAdapter() { baseAdapter.callBids = function(params) { if (params && params.bids && utils.isArray(params.bids) && params.bids.length) { - const data = { - placements: [], - time: Date.now(), - user: {}, - url: utils.getTopWindowUrl(), - referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true - }; - - const bids = params.bids || []; - for (let i = 0; i < bids.length; i++) { - const bid = bids[i]; - - bidIds.push(bid.bidId); - - const bid_data = { - networkId: bid.params.networkId, - siteId: bid.params.siteId, - zoneIds: bid.params.zoneIds, - campaignId: bid.params.campaignId, - flightId: bid.params.flightId, - adId: bid.params.adId, - divName: bid.bidId, - adTypes: bid.adTypes || getSize(bid.sizes) + if (!window.SMARTSYNC) { + _callBids(params); + } else { + window[SMARTSYNC_CALLBACK] = function() { + window[SMARTSYNC_CALLBACK] = function() {}; + _callBids(params); }; - if (bid_data.networkId && bid_data.siteId) { - data.placements.push(bid_data); - } - } + const siteId = params.bids[0].params.siteId; + _appendScript(SMARTSYNC_BASE_URI + '/' + siteId + '.js'); - if (data.placements.length) { - ajax(BASE_URI, _responseCallback, JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); + const sstimeout = window.SMARTSYNC_TIMEOUT || ((params.timeout || 500) / 2); + setTimeout(function() { + var cb = window[SMARTSYNC_CALLBACK]; + window[SMARTSYNC_CALLBACK] = function() {}; + cb(); + }, sstimeout); } } }; + function _appendScript(src) { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = src; + document.getElementsByTagName('head')[0].appendChild(script); + } + + function _callBids(params) { + const data = { + placements: [], + time: Date.now(), + user: {}, + url: utils.getTopWindowUrl(), + referrer: document.referrer, + enableBotFiltering: true, + includePricingData: true + }; + + const bids = params.bids || []; + + for (let i = 0; i < bids.length; i++) { + const bid = bids[i]; + + bidIds.push(bid.bidId); + + const placement = Object.assign({ + divName: bid.bidId, + adTypes: bid.adTypes || getSize(bid.sizes) + }, bid.params); + + if (placement.networkId && placement.siteId) { + data.placements.push(placement); + } + } + + if (data.placements.length) { + ajax(SERVERBID_BASE_URI, _responseCallback, JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); + } + } + function _responseCallback(result) { let bid; let bidId; diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 21d1561192a..d53fb0d92db 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -48,6 +48,20 @@ var SharethroughAdapter = function SharethroughAdapter() { function _strcallback(bidObj, bidResponse) { try { bidResponse = JSON.parse(bidResponse); + } catch (e) { + _handleInvalidBid(bidObj); + return; + } + + if (bidResponse.creatives && bidResponse.creatives.length > 0) { + _handleBid(bidObj, bidResponse); + } else { + _handleInvalidBid(bidObj); + } + } + + function _handleBid(bidObj, bidResponse) { + try { const bidId = bidResponse.bidId; const bid = bidfactory.createBid(1, bidObj); bid.bidderCode = STR_BIDDER_CODE; @@ -91,6 +105,7 @@ var SharethroughAdapter = function SharethroughAdapter() { function _handleInvalidBid(bidObj) { const bid = bidfactory.createBid(2, bidObj); + bid.bidderCode = STR_BIDDER_CODE; bidmanager.addBidResponse(bidObj.placementCode, bid); } diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 3f8a028a947..e8ed973fe5f 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -57,8 +57,9 @@ function Spotx() { bid.url = adServerKVPs.spotx_ad_key; bid.cur = 'USD'; bid.bidderCode = 'spotx'; - bid.height = bidReq.sizes[0][1]; - bid.width = bidReq.sizes[0][0]; + var sizes = utils.isArray(bidReq.sizes[0]) ? bidReq.sizes[0] : bidReq.sizes; + bid.height = sizes[1]; + bid.width = sizes[0]; resp.bids.push(bid); KVP_Object = adServerKVPs; handleResponse(resp); @@ -86,8 +87,9 @@ function Spotx() { bid.vastUrl = url; bid.ad = url; - bid.width = bidReq.sizes[0][0]; - bid.height = bidReq.sizes[0][1]; + var sizes = utils.isArray(bidReq.sizes[0]) ? bidReq.sizes[0] : bidReq.sizes; + bid.height = sizes[1]; + bid.width = sizes[0]; } return bid; diff --git a/modules/springserveBidAdapter.js b/modules/springserveBidAdapter.js index f84229b8fd6..d54702a230f 100644 --- a/modules/springserveBidAdapter.js +++ b/modules/springserveBidAdapter.js @@ -111,6 +111,6 @@ SpringServeAdapter = function SpringServeAdapter() { }; }; -adaptermanager.registerBidAdapter(new SpringServeAdapter(), 'springserveg'); +adaptermanager.registerBidAdapter(new SpringServeAdapter(), 'springserve'); module.exports = SpringServeAdapter; diff --git a/modules/yieldbotBidAdapter.js b/modules/yieldbotBidAdapter.js index 82858827af1..4f874a82502 100644 --- a/modules/yieldbotBidAdapter.js +++ b/modules/yieldbotBidAdapter.js @@ -23,7 +23,6 @@ function YieldbotAdapter() { AVAILABLE: 1, EMPTY: 2 }, - definedSlots: [], pageLevelOption: false, /** * Builds the Yieldbot creative tag. @@ -71,6 +70,29 @@ function YieldbotAdapter() { bid.bidderCode = 'yieldbot'; return bid; }, + /** + * Unique'ify slot sizes for a Yieldbot bid request
+ * Bids may refer to a slot and dimension multiple times on a page, but should exist once in the request. + * @param {Array} sizes An array of sizes to deduplicate + * @private + */ + getUniqueSlotSizes: function(sizes) { + var newSizes = []; + var hasSize = {}; + if (utils.isArray(sizes)) { + for (var idx = 0; idx < sizes.length; idx++) { + var bidSize = sizes[idx] || ''; + if (bidSize && utils.isStr(bidSize) && !hasSize[bidSize]) { + var nSize = bidSize.split('x'); + if (nSize.length > 1) { + newSizes.push([nSize[0], nSize[1]]); + } + hasSize[bidSize] = true; + } + } + } + return newSizes; + }, /** * Yieldbot implementation of {@link module:adaptermanger.callBids} * @param {Object} params - Adapter bid configuration object @@ -84,8 +106,9 @@ function YieldbotAdapter() { ybotq.push(function () { var yieldbot = window.yieldbot; - // Empty defined slots bidId array - ybotlib.definedSlots = []; + // Empty defined slot bids object + ybotlib.bids = {}; + ybotlib.parsedBidSizes = {}; // Iterate through bids to obtain Yieldbot slot config // - Slot config can be different between initial and refresh requests var psn = 'ERROR_PREBID_DEFINE_YB_PSN'; @@ -94,23 +117,33 @@ function YieldbotAdapter() { var bid = v; // bidder params config: http://prebid.org/dev-docs/bidders/yieldbot.html // - last psn wins - psn = (bid.params && bid.params.psn) || psn; - var slotName = (bid.params && bid.params.slot) || 'ERROR_PREBID_DEFINE_YB_SLOT'; - - slots[slotName] = bid.sizes || []; - ybotlib.definedSlots.push(bid.bidId); + psn = bid.params && bid.params.psn ? bid.params.psn : psn; + var slotName = bid.params && bid.params.slot ? bid.params.slot : 'ERROR_PREBID_DEFINE_YB_SLOT'; + var parsedSizes = utils.parseSizesInput(bid.sizes) || []; + slots[slotName] = slots[slotName] || []; + slots[slotName] = slots[slotName].concat(parsedSizes); + ybotlib.bids[bid.bidId] = bid; + ybotlib.parsedBidSizes[bid.bidId] = parsedSizes; }); + for (var bidSlots in slots) { + if (slots.hasOwnProperty(bidSlots)) { + // The same slot name and size may be used for multiple bids. Get unique sizes + // for the request. + slots[bidSlots] = ybotlib.getUniqueSlotSizes(slots[bidSlots]); + } + } + if (yieldbot._initialized !== true) { yieldbot.pub(psn); for (var slotName in slots) { if (slots.hasOwnProperty(slotName)) { - yieldbot.defineSlot(slotName, { sizes: slots[slotName] || [] }); + yieldbot.defineSlot(slotName, { sizes: slots[slotName] }); } } yieldbot.enableAsync(); yieldbot.go(); - } else { + } else if (!utils.isEmpty(slots)) { yieldbot.nextPageview(slots); } }); @@ -128,27 +161,37 @@ function YieldbotAdapter() { */ handleUpdateState: function () { var yieldbot = window.yieldbot; - utils._each(ybotlib.definedSlots, function (v) { - var ybRequest; - var adapterConfig; - - ybRequest = $$PREBID_GLOBAL$$._bidsRequested - .find(bidderRequest => bidderRequest.bidderCode === 'yieldbot'); - - adapterConfig = ybRequest && ybRequest.bids ? ybRequest.bids.find(bid => bid.bidId === v) : null; - - 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); + var slotUsed = {}; + + for (var bidId in ybotlib.bids) { + if (ybotlib.bids.hasOwnProperty(bidId)) { + var bidRequest = ybotlib.bids[bidId] || null; + + if (bidRequest && bidRequest.params && bidRequest.params.slot) { + var placementCode = bidRequest.placementCode || 'ERROR_YB_NO_PLACEMENT'; + var criteria = yieldbot.getSlotCriteria(bidRequest.params.slot); + var requestedSizes = ybotlib.parsedBidSizes[bidId] || []; + + var slotSizeOk = false; + for (var idx = 0; idx < requestedSizes.length; idx++) { + var requestedSize = requestedSizes[idx]; + + if (!slotUsed[criteria.ybot_slot] && requestedSize === criteria.ybot_size) { + slotSizeOk = true; + slotUsed[criteria.ybot_slot] = true; + break; + } + } + var bid = ybotlib.buildBid(slotSizeOk ? criteria : { ybot_ad: 'n' }); + bidmanager.addBidResponse(placementCode, bid); + } } - }); + } } }; return { - callBids: ybotlib.callBids + callBids: ybotlib.callBids, + getUniqueSlotSizes: ybotlib.getUniqueSlotSizes }; } diff --git a/package.json b/package.json index db8e8be5a47..3d6cfaaff0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.28.0-pre", + "version": "0.28.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -61,7 +61,7 @@ "gulp-util": "^3.0.0", "gulp-webdriver": "^1.0.1", "istanbul": "^0.4.5", - "istanbul-instrumenter-loader": "^2.0.0", + "istanbul-instrumenter-loader": "^3.0.0", "json-loader": "^0.5.1", "karma": "^1.7.0", "karma-babel-preprocessor": "^6.0.1", diff --git a/src/bidfactory.js b/src/bidfactory.js index e88eb467413..ff57abb8a39 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -49,6 +49,6 @@ function Bid(statusCode, bidRequest) { } // Bid factory function. -exports.createBid = function () { - return new Bid(...arguments); +exports.createBid = function (statusCode, bidRequest) { + return new Bid(statusCode, bidRequest); }; diff --git a/src/bidmanager.js b/src/bidmanager.js index 757383386c6..bf8ef86246c 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -120,8 +120,8 @@ exports.addBidResponse = function (adUnitCode, bid) { utils.logError(errorMessage('Native bid missing some required properties.')); return false; } - if (bid.mediaType === 'video' && !(bid.vastUrl || bid.vastPayload)) { - utils.logError(errorMessage(`Video bid has no vastUrl or vastPayload property.`)); + if (bid.mediaType === 'video' && !(bid.vastUrl || bid.vastXml)) { + utils.logError(errorMessage(`Video bid has no vastUrl or vastXml property.`)); return false; } if (bid.mediaType === 'banner' && !validBidSize(bid)) { @@ -191,7 +191,11 @@ exports.addBidResponse = function (adUnitCode, bid) { bid.renderer.setRender(adUnitRenderer.render); } - const priceStringsObj = getPriceBucketString(bid.cpm, config.getConfig('customPriceBucket')); + const priceStringsObj = getPriceBucketString( + bid.cpm, + config.getConfig('customPriceBucket'), + config.getConfig('currency.granularityMultiplier') + ); bid.pbLg = priceStringsObj.low; bid.pbMg = priceStringsObj.med; bid.pbHg = priceStringsObj.high; diff --git a/src/config.js b/src/config.js index 42995369ce9..f9c0e24e334 100644 --- a/src/config.js +++ b/src/config.js @@ -7,7 +7,7 @@ * Defining and access properties in this way is now deprecated, but these will * continue to work during a deprecation window. */ -import { isValidePriceConfig } from './cpmBucketManager'; +import { isValidPriceConfig } from './cpmBucketManager'; const utils = require('./utils'); const DEFAULT_DEBUG = false; @@ -86,7 +86,7 @@ export function newConfig() { this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; } else if (typeof val === 'object') { this._customPriceBucket = val; - this._priceGranularity = GRANULARITY_OPTIONS.MEDIUM; + this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; utils.logMessage('Using custom price granularity'); } } @@ -134,7 +134,7 @@ export function newConfig() { utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); } } else if (typeof val === 'object') { - if (!isValidePriceConfig(val)) { + if (!isValidPriceConfig(val)) { utils.logError('Invalid custom price value passed to `setPriceGranularity()`'); return false; } @@ -144,8 +144,8 @@ export function newConfig() { /* * Returns configuration object if called without parameters, - * or single configuration property if given a string matching a configuartion - * property name. + * or single configuration property if given a string matching a configuration + * property name. Allows deep access e.g. getConfig('currency.adServerCurrency') * * If called with callback parameter, or a string and a callback parameter, * subscribes to configuration updates. See `subscribe` function for usage. @@ -153,7 +153,7 @@ export function newConfig() { function getConfig(...args) { if (args.length <= 1 && typeof args[0] !== 'function') { const option = args[0]; - return option ? config[option] : config; + return option ? utils.deepAccess(config, option) : config; } return subscribe(...args); diff --git a/src/cpmBucketManager.js b/src/cpmBucketManager.js index 5728601d245..f9515994342 100644 --- a/src/cpmBucketManager.js +++ b/src/cpmBucketManager.js @@ -57,26 +57,25 @@ const _autoPriceConfig = { }] }; -function getPriceBucketString(cpm, customConfig) { - let cpmFloat = 0; - cpmFloat = parseFloat(cpm); +function getPriceBucketString(cpm, customConfig, granularityMultiplier = 1) { + let cpmFloat = parseFloat(cpm); if (isNaN(cpmFloat)) { cpmFloat = ''; } return { - low: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _lgPriceConfig), - med: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _mgPriceConfig), - high: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _hgPriceConfig), - auto: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _autoPriceConfig), - dense: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _densePriceConfig), - custom: (cpmFloat === '') ? '' : getCpmStringValue(cpm, customConfig) + low: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _lgPriceConfig, granularityMultiplier), + med: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _mgPriceConfig, granularityMultiplier), + high: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _hgPriceConfig, granularityMultiplier), + auto: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _autoPriceConfig, granularityMultiplier), + dense: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _densePriceConfig, granularityMultiplier), + custom: (cpmFloat === '') ? '' : getCpmStringValue(cpm, customConfig, granularityMultiplier) }; } -function getCpmStringValue(cpm, config) { +function getCpmStringValue(cpm, config, granularityMultiplier) { let cpmStr = ''; - if (!isValidePriceConfig(config)) { + if (!isValidPriceConfig(config)) { return cpmStr; } const cap = config.buckets.reduce((prev, curr) => { @@ -88,20 +87,20 @@ function getCpmStringValue(cpm, config) { 'max': 0, }); let bucket = config.buckets.find(bucket => { - if (cpm > cap.max) { + if (cpm > cap.max * granularityMultiplier) { const precision = bucket.precision || _defaultPrecision; - cpmStr = bucket.max.toFixed(precision); - } else if (cpm <= bucket.max && cpm >= bucket.min) { + cpmStr = (bucket.max * granularityMultiplier).toFixed(precision); + } else if (cpm <= bucket.max * granularityMultiplier && cpm >= bucket.min * granularityMultiplier) { return bucket; } }); if (bucket) { - cpmStr = getCpmTarget(cpm, bucket.increment, bucket.precision); + cpmStr = getCpmTarget(cpm, bucket.increment, bucket.precision, granularityMultiplier); } return cpmStr; } -function isValidePriceConfig(config) { +function isValidPriceConfig(config) { if (utils.isEmpty(config) || !config.buckets || !Array.isArray(config.buckets)) { return false; } @@ -114,12 +113,12 @@ function isValidePriceConfig(config) { return isValid; } -function getCpmTarget(cpm, increment, precision) { +function getCpmTarget(cpm, increment, precision, granularityMultiplier) { if (!precision) { precision = _defaultPrecision; } - let bucketSize = 1 / increment; + let bucketSize = 1 / (increment * granularityMultiplier); return (Math.floor(cpm * bucketSize) / bucketSize).toFixed(precision); } -export { getPriceBucketString, isValidePriceConfig }; +export { getPriceBucketString, isValidPriceConfig }; diff --git a/src/prebid.js b/src/prebid.js index bc61fa1592b..8a8eca318d8 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -116,7 +116,7 @@ function setRenderSize(doc, width, height) { * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for * @alias module:$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr - * @return {array} returnObj return bids array + * @return {Array} returnObj return bids array */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr', arguments); @@ -133,7 +133,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for - * @returns {object} returnObj return bids + * @returns {Object} returnObj return bids */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; @@ -141,7 +141,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { /** * returns all ad server targeting for all ad units - * @return {object} Map of adUnitCodes and targeting values [] + * @return {Object} Map of adUnitCodes and targeting values [] * @alias module:$$PREBID_GLOBAL$$.getAdserverTargeting */ @@ -168,7 +168,7 @@ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { /** * This function returns the bid responses at the given moment. * @alias module:$$PREBID_GLOBAL$$.getBidResponses - * @return {object} map | object that contains the bidResponses + * @return {Object} map | object that contains the bidResponses */ $$PREBID_GLOBAL$$.getBidResponses = function () { @@ -193,7 +193,7 @@ $$PREBID_GLOBAL$$.getBidResponses = function () { /** * Returns bidResponses for the specified adUnitCode - * @param {String} adUnitCode adUnitCode + * @param {string} adUnitCode adUnitCode * @alias module:$$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode * @return {Object} bidResponse object */ @@ -318,7 +318,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { /** * Remove adUnit from the $$PREBID_GLOBAL$$ configuration - * @param {String} adUnitCode the adUnitCode to remove + * @param {string} adUnitCode the adUnitCode to remove * @alias module:$$PREBID_GLOBAL$$.removeAdUnit */ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { @@ -342,11 +342,11 @@ $$PREBID_GLOBAL$$.clearAuction = function() { }; /** - * - * @param bidsBackHandler - * @param timeout - * @param adUnits - * @param adUnitCodes + * @param {Object} requestOptions + * @param {function} requestOptions.bidsBackHandler + * @param {number} requestOptions.timeout + * @param {Array} requestOptions.adUnits + * @param {Array} requestOptions.adUnitCodes */ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes } = {}) { events.emit('requestBids'); @@ -443,9 +443,9 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { }; /** - * @param {String} event the name of the event + * @param {string} event the name of the event * @param {Function} handler a callback to set on event - * @param {String} id an identifier in the context of the event + * @param {string} id an identifier in the context of the event * * This API call allows you to register a callback to handle a Prebid.js event. * An optional `id` parameter provides more finely-grained event callback registration. @@ -473,9 +473,9 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { }; /** - * @param {String} event the name of the event + * @param {string} event the name of the event * @param {Function} handler a callback to remove from the event - * @param {String} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) + * @param {string} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) */ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); @@ -488,10 +488,10 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { /** * Add a callback event - * @param {String} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" + * @param {string} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" * @param {Function} func function to execute. Parameters passed into the function: (bidResObj), [adUnitCode]); * @alias module:$$PREBID_GLOBAL$$.addCallback - * @returns {String} id for callback + * @returns {string} id for callback * * @deprecated This function will be removed in Prebid 1.0 * Please use onEvent instead. @@ -514,7 +514,7 @@ $$PREBID_GLOBAL$$.addCallback = function (eventStr, func) { * Remove a callback event * //@param {string} cbId id of the callback to remove * @alias module:$$PREBID_GLOBAL$$.removeCallback - * @returns {String} id for callback + * @returns {string} id for callback * * @deprecated This function will be removed in Prebid 1.0 * Please use offEvent instead. @@ -527,9 +527,8 @@ $$PREBID_GLOBAL$$.removeCallback = function (/* cbId */) { /** * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) - * @param {[type]} bidderAdaptor [description] - * @param {[type]} bidderCode [description] - * @return {[type]} [description] + * @param {Function} bidderAdaptor [description] + * @param {string} bidderCode [description] */ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); @@ -542,7 +541,7 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { /** * Wrapper to register analyticsAdapter externally (adaptermanager.registerAnalyticsAdapter()) - * @param {[type]} options [description] + * @param {Object} options [description] */ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); @@ -568,8 +567,8 @@ $$PREBID_GLOBAL$$.bidsAvailableForAdapter = function (bidderCode) { /** * Wrapper to bidfactory.createBid() - * @param {[type]} statusCode [description] - * @return {[type]} [description] + * @param {string} statusCode [description] + * @return {Object} bidResponse [description] */ $$PREBID_GLOBAL$$.createBid = function (statusCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.createBid', arguments); @@ -578,8 +577,8 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { /** * Wrapper to bidmanager.addBidResponse - * @param {[type]} adUnitCode [description] - * @param {[type]} bid [description] + * @param {string} adUnitCode [description] + * @param {Object} bid [description] * * @deprecated This function will be removed in Prebid 1.0 * Each bidder will be passed a reference to addBidResponse function in callBids as an argument. @@ -593,9 +592,8 @@ $$PREBID_GLOBAL$$.addBidResponse = function (adUnitCode, bid) { /** * Wrapper to adloader.loadScript - * @param {[type]} tagSrc [description] + * @param {string} tagSrc [description] * @param {Function} callback [description] - * @return {[type]} [description] */ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.loadScript', arguments); @@ -604,7 +602,7 @@ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { /** * Will enable sending a prebid.js to data provider specified - * @param {object} config object {provider : 'string', options : {}} + * @param {Object} config object {provider : 'string', options : {}} */ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { if (config && !utils.isEmpty(config)) { @@ -626,7 +624,7 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { /** * Sets a default price granularity scheme. - * @param {String|Object} granularity - the granularity scheme. + * @param {string|Object} granularity - the granularity scheme. * @deprecated - use pbjs.setConfig({ priceGranularity: }) * "low": $0.50 increments, capped at $5 CPM * "medium": $0.10 increments, capped at $20 CPM (the default) @@ -656,7 +654,7 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () { /** * Build master video tag from publishers adserver tag * @param {string} adserverTag default url - * @param {object} options options for video tag + * @param {Object} options options for video tag * * @deprecated Include the dfpVideoSupport module in your build, and use the $$PREBID_GLOBAL$$.adservers.dfp.buildVideoAdUrl function instead. * This function will be removed in Prebid 1.0. @@ -703,7 +701,7 @@ $$PREBID_GLOBAL$$.setBidderSequence = adaptermanager.setBidderSequence * Get array of highest cpm bids for all adUnits, or highest cpm bid * object for the given adUnit * @param {string} adUnitCode - optional ad unit code - * @return {array} array containing highest cpm bid object(s) + * @return {Array} array containing highest cpm bid object(s) */ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); @@ -748,13 +746,13 @@ $$PREBID_GLOBAL$$.setS2SConfig = function(options) { /** * Get Prebid config options - * @param {object} options + * @param {Object} options */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; /** * Set Prebid config options - * @param {object} options + * @param {Object} options */ $$PREBID_GLOBAL$$.setConfig = config.setConfig; @@ -775,14 +773,15 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * by prebid once it's done loading. If it runs after prebid loads, then this monkey-patch causes their * function to execute immediately. * - * @param {function} cmd A function which takes no arguments. This is guaranteed to run exactly once, and only after - * the Prebid script has been fully loaded. + * @memberof $$PREBID_GLOBAL$$ + * @param {function} command A function which takes no arguments. This is guaranteed to run exactly once, and only after + * the Prebid script has been fully loaded. * @alias module:$$PREBID_GLOBAL$$.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function(cmd) { - if (typeof cmd === 'function') { +$$PREBID_GLOBAL$$.cmd.push = function(command) { + if (typeof command === 'function') { try { - cmd.call(); + command.call(); } catch (e) { utils.logError('Error processing command :' + e.message); } diff --git a/src/utils.js b/src/utils.js index 0a81503e1d3..0c1846b18ce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -157,6 +157,8 @@ export function parseGPTSingleSizeArray(singleSize) { exports.getTopWindowLocation = function () { let location; try { + // force an exception in x-domain enviornments. #1509 + window.top.location.toString(); location = window.top.location; } catch (e) { location = window.location; @@ -677,3 +679,20 @@ export function groupBy(xs, key) { return rv; }, {}); } + +/** + * deepAccess utility function useful for doing safe access (will not throw exceptions) of deep object paths. + * @param {object} obj The object containing the values you would like to access. + * @param {string|number} path Object path to the value you would like to access. Non-strings are coerced to strings. + * @returns {*} The value found at the specified object path, or undefined if path is not found. + */ +export function deepAccess(obj, path) { + path = String(path).split('.'); + for (let i = 0; i < path.length; i++) { + obj = obj[path[i]]; + if (typeof obj === 'undefined') { + return; + } + } + return obj; +} diff --git a/src/videoCache.js b/src/videoCache.js index 73d249c13f0..fe126fad6e0 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -20,7 +20,7 @@ const BASE_URL = 'https://prebid.adnxs.com/pbc/v1/cache' /** * @typedef {object} CacheablePayloadBid - * @property {string} vastPayload Some VAST XML which loads an ad in a video player. + * @property {string} vastXml Some VAST XML which loads an ad in a video player. */ /** @@ -58,7 +58,7 @@ function wrapURI(uri) { * @param {CacheableBid} bid */ function toStorageRequest(bid) { - const vastValue = bid.vastPayload ? bid.vastPayload : wrapURI(bid.vastUrl); + const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl); return { type: 'xml', value: vastValue diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 59b812acc5c..8108da3c555 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1370,3 +1370,13 @@ export function getBidRequestedPayload() { 'start': 1465426155412 }; } + +export function getCurrencyRates() { + return { + 'dataAsOf': '2017-04-25', + 'conversions': { + 'GBP': { 'CNY': 8.8282, 'JPY': 141.7, 'USD': 1.2824 }, + 'USD': { 'CNY': 6.8842, 'GBP': 0.7798, 'JPY': 110.49 } + } + }; +} diff --git a/test/fixtures/video/vastPayloadResponse.json b/test/fixtures/video/vastPayloadResponse.json index 5ffdf99350b..9b621c21d30 100644 --- a/test/fixtures/video/vastPayloadResponse.json +++ b/test/fixtures/video/vastPayloadResponse.json @@ -8,6 +8,6 @@ "height": 480, "mediaType": "video", "requestId": "6172477f-987f-4523-a967-fa6d7a434ddf", - "vastPayload": "", + "vastXml": "", "width": 640 } diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 77357df6d9e..041be4591fc 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -128,7 +128,7 @@ describe('config API', () => { }] }; setConfig({ priceGranularity: goodConfig }); - expect(getConfig('priceGranularity')).to.be.equal('medium'); + expect(getConfig('priceGranularity')).to.be.equal('custom'); expect(getConfig('customPriceBucket')).to.equal(goodConfig); }); diff --git a/test/spec/cpmBucketManager_spec.js b/test/spec/cpmBucketManager_spec.js index 1590a647417..f1f7d697397 100644 --- a/test/spec/cpmBucketManager_spec.js +++ b/test/spec/cpmBucketManager_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {getPriceBucketString, isValidePriceConfig} from 'src/cpmBucketManager'; +import {getPriceBucketString, isValidPriceConfig} from 'src/cpmBucketManager'; let cpmFixtures = require('test/fixtures/cpmInputsOutputs.json'); describe('cpmBucketManager', () => { @@ -35,6 +35,29 @@ describe('cpmBucketManager', () => { expect(JSON.stringify(output)).to.deep.equal(expected); }); + it('gets the correct custom bucket strings in non-USD currency', () => { + let cpm = 16.50908 * 110.49; + let customConfig = { + 'buckets': [{ + 'precision': 4, + 'min': 0, + 'max': 3, + 'increment': 0.01, + }, + { + 'precision': 4, + 'min': 3, + 'max': 18, + 'increment': 0.05, + 'cap': true + } + ] + }; + let expected = '{"low":"552.45","med":"1823.09","high":"1823.09","auto":"1823.09","dense":"1823.09","custom":"1823.0850"}'; + let output = getPriceBucketString(cpm, customConfig, 110.49); + expect(JSON.stringify(output)).to.deep.equal(expected); + }); + it('checks whether custom config is valid', () => { let badConfig = { 'buckets': [{ @@ -51,6 +74,6 @@ describe('cpmBucketManager', () => { ] }; - expect(isValidePriceConfig(badConfig)).to.be.false; + expect(isValidPriceConfig(badConfig)).to.be.false; }); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 0d2d1e19b68..9d77b4faca5 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -1,11 +1,11 @@ import { assert } from 'chai'; -import * as utils from '../../../src/utils'; -import adLoader from '../../../src/adloader'; -import bidManager from '../../../src/bidmanager'; -import adapter from '../../../modules/adformBidAdapter'; +import * as utils from 'src/utils'; +import adLoader from 'src/adloader'; +import bidManager from 'src/bidmanager'; +import AdformAdapter from 'modules/adformBidAdapter'; describe('Adform adapter', () => { - let _adapter, sandbox; + let _adformAdapter, sandbox; describe('request', () => { it('should create callback method on PREBID_GLOBAL', () => { @@ -112,11 +112,11 @@ describe('Adform adapter', () => { beforeEach(() => { var transactionId = 'transactionId'; - _adapter = adapter(); + _adformAdapter = new AdformAdapter(); utils.getUniqueIdentifierStr = () => 'callback'; sandbox = sinon.sandbox.create(); sandbox.stub(adLoader, 'loadScript'); - _adapter.callBids({ + _adformAdapter.callBids({ bids: [ { bidId: 'abc', diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js new file mode 100644 index 00000000000..fa55bf92e2e --- /dev/null +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -0,0 +1,212 @@ +import { expect } from 'chai'; +import Adapter from 'modules/adxcgBidAdapter'; +import bidmanager from 'src/bidmanager'; +import * as url from 'src/url'; + +const REQUEST = { + 'bidderCode': 'adxcg', + 'bids': [ + { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1', + }, + 'sizes': [ + [300, 250], + [640, 360], + [1, 1] + ], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2' + } + ] +}; + +const RESPONSE = [{ + 'bidId': '84ab500420319d', + 'width': 300, + 'height': 250, + 'creativeId': '42', + 'cpm': 0.45, + 'ad': '' +}] + +const VIDEO_RESPONSE = [{ + 'bidId': '84ab500420319d', + 'width': 640, + 'height': 360, + 'creativeId': '42', + 'cpm': 0.45, + 'vastUrl': 'vastContentUrl' +}] + +const NATIVE_RESPONSE = [{ + 'bidId': '84ab500420319d', + 'width': 0, + 'height': 0, + 'creativeId': '42', + 'cpm': 0.45, + 'nativeResponse': { + 'assets': [{ + 'id': 1, + 'required': 0, + 'title': { + 'text': 'titleContent' + } + }, { + 'id': 2, + 'required': 0, + 'img': { + 'url': 'imageContent', + 'w': 600, + 'h': 600 + } + }, { + 'id': 3, + 'required': 0, + 'data': { + 'label': 'DESC', + 'value': 'descriptionContent' + } + }, { + 'id': 0, + 'required': 0, + 'data': { + 'label': 'SPONSORED', + 'value': 'sponsoredByContent' + } + }], + 'link': { + 'url': 'linkContent' + }, + 'imptrackers': ['impressionTracker1', 'impressionTracker2'] + } +}] + +describe('AdxcgAdapter', () => { + let adapter; + + beforeEach(() => adapter = new Adapter()); + + describe('request function', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + it('creates a valid adxcg request url', () => { + adapter.callBids(REQUEST); + + let parsedRequestUrl = url.parse(requests[0].url); + + expect(parsedRequestUrl.hostname).to.equal('ad-emea.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20141124'); + expect(query.adzoneid).to.equal('1'); + expect(query.format).to.equal('300x250|640x360|1x1'); + expect(query.jsonp).to.be.empty; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + }); + }); + + describe('response handler', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore() + bidmanager.addBidResponse.restore(); + }); + + it('handles regular responses', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(bidResponse.bidderCode).to.equal('adxcg'); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse.statusMessage).to.equal('Bid available'); + expect(bidResponse.adId).to.equal('84ab500420319d'); + expect(bidResponse.mediaType).to.equal('banner'); + expect(bidResponse.creative_id).to.equal('42'); + expect(bidResponse.code).to.equal('adxcg'); + expect(bidResponse.cpm).to.equal(0.45); + expect(bidResponse.ad).to.equal(''); + }); + + it('handles video responses', () => { + server.respondWith(JSON.stringify(VIDEO_RESPONSE)); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(bidResponse.bidderCode).to.equal('adxcg'); + expect(bidResponse.width).to.equal(640); + expect(bidResponse.height).to.equal(360); + expect(bidResponse.statusMessage).to.equal('Bid available'); + expect(bidResponse.adId).to.equal('84ab500420319d'); + expect(bidResponse.mediaType).to.equal('video'); + expect(bidResponse.creative_id).to.equal('42'); + expect(bidResponse.code).to.equal('adxcg'); + expect(bidResponse.cpm).to.equal(0.45); + expect(bidResponse.vastUrl).to.equal('vastContentUrl'); + expect(bidResponse.descriptionUrl).to.equal('vastContentUrl'); + }); + + it('handles native responses', () => { + server.respondWith(JSON.stringify(NATIVE_RESPONSE)); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(bidResponse.bidderCode).to.equal('adxcg'); + expect(bidResponse.width).to.equal(0); + expect(bidResponse.height).to.equal(0); + expect(bidResponse.statusMessage).to.equal('Bid available'); + expect(bidResponse.adId).to.equal('84ab500420319d'); + expect(bidResponse.mediaType).to.equal('native'); + expect(bidResponse.creative_id).to.equal('42'); + expect(bidResponse.code).to.equal('adxcg'); + expect(bidResponse.cpm).to.equal(0.45); + + expect(bidResponse.native.clickUrl).to.equal('linkContent'); + expect(bidResponse.native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); + expect(bidResponse.native.title).to.equal('titleContent'); + expect(bidResponse.native.image).to.equal('imageContent'); + expect(bidResponse.native.body).to.equal('descriptionContent'); + expect(bidResponse.native.sponsoredBy).to.equal('sponsoredByContent'); + }); + + it('handles nobid responses', () => { + server.respondWith('[]'); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(bidResponse.statusMessage).to.equal('Bid returned empty or error response'); + }); + }); +}); diff --git a/test/spec/modules/aerservBidAdapter_spec.js b/test/spec/modules/aerservBidAdapter_spec.js new file mode 100644 index 00000000000..be0f6393063 --- /dev/null +++ b/test/spec/modules/aerservBidAdapter_spec.js @@ -0,0 +1,213 @@ +import {expect} from 'chai'; +import AerServAdapter from 'modules/aerservBidAdapter'; +import bidmanager from 'src/bidmanager'; + +const BASE_REQUEST = JSON.stringify({ + bidderCode: 'aerserv', + requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53', + bidderRequestId: '1f8c8c03de01f9', + bids: [ + { + bidder: 'aerserv', + params: { + plc: '480', + }, + placementCode: 'adunit-1', + transactionId: 'a0e033af-f50c-4a7e-aeed-c01c5f709848', + sizes: [[300, 250], [300, 600]], + bidId: '2f4a69463b3bc9', + bidderRequestId: '1f8c8c03de01f9', + requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53' + } + ] +}); + +describe('AerServ Adapter', () => { + let adapter; + let bidmanagerStub; + + beforeEach(() => { + adapter = new AerServAdapter(); + bidmanagerStub = sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + bidmanager.addBidResponse.restore(); + }); + + describe('callBids()', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => { + xhr.restore(); + }); + + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + + it('should not add bid responses with no bids to call', () => { + adapter.callBids({}); + + sinon.assert.notCalled(bidmanager.addBidResponse); + }); + + it('requires plc parameter to make request', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0].params = {}; + adapter.callBids(bidRequest); + expect(requests).to.be.empty; + }); + + it('sends requests to normal endpoint for non-video requests', () => { + adapter.callBids(JSON.parse(BASE_REQUEST)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('/as/json/pbjs/v1'); + }); + + it('sends requests to video endpoint for video requests', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0]['mediaType'] = 'video'; + bidRequest.bids[0]['video'] = {}; + adapter.callBids(bidRequest); + expect(requests[0].url).to.include('/as/json/pbjsvast/v1'); + }); + + it('properly adds video parameters to the request', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0]['mediaType'] = 'video'; + bidRequest.bids[0].params['video'] = { videoParam: 'videoValue' }; + adapter.callBids(bidRequest); + expect(requests[0].url).to.include('videoParam=videoValue'); + }); + + it('parses the first size for video requests', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0]['mediaType'] = 'video'; + adapter.callBids(bidRequest); + expect(requests[0].url).to.include('vpw=300'); + expect(requests[0].url).to.include('vph=250'); + }); + + it('sends requests to production by default', () => { + adapter.callBids(JSON.parse(BASE_REQUEST)); + expect(requests[0].url).to.include('//ads.aerserv.com'); + }); + + it('sends requests to the specified endpoint when \'env\' is provided', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0].params['env'] = 'dev'; + adapter.callBids(bidRequest); + expect(requests[0].url).to.include('//dev-ads.aerserv.com'); + }); + }); + + describe('response handling', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + }); + + afterEach(() => { + server.restore(); + }); + + it('responds with an empty bid without required parameters', () => { + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0].params = {}; + adapter.callBids(bidRequest); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + it('responds with an empty bid on empty response', () => { + server.respondWith(''); + + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + it('responds with an empty bid on un-parseable JSON response', () => { + server.respondWith('{\"bad\":\"json}'); + + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + it('responds with a valid bid returned ad', () => { + server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, adm: 'sweet ad markup'})); + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(1); + }); + + it('responds with a valid bid from returned ad', () => { + server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, vastUrl: 'sweet URL where VAST is at'})); + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0]['mediaType'] = 'video'; + bidRequest.bids[0]['video'] = {}; + adapter.callBids(bidRequest); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(1); + }); + + it('responds with empty bid if response has no ad', () => { + server.respondWith(JSON.stringify({error: 'no ads'})); + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + // things that should never occur + it('responds with empty bid if response has 0 or below cpm', () => { + server.respondWith(JSON.stringify({cpm: 0, w: 320, h: 50, adm: 'sweet ad markup'})); + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + it('responds with empty bid if response has no markup', () => { + server.respondWith(JSON.stringify({cpm: 5.0, w: 320, h: 50})); + adapter.callBids(JSON.parse(BASE_REQUEST)); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + + it('responds with an empty bid if response has no video markup', () => { + server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50})); + let bidRequest = JSON.parse(BASE_REQUEST); + bidRequest.bids[0]['mediaType'] = 'video'; + bidRequest.bids[0]['video'] = {}; + adapter.callBids(bidRequest); + server.respond(); + let bid = bidmanagerStub.getCall(0).args[1]; + sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bid.getStatusCode()).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/appnexusAstBidAdapter_spec.js b/test/spec/modules/appnexusAstBidAdapter_spec.js index 310cd6a1884..3756c2a6651 100644 --- a/test/spec/modules/appnexusAstBidAdapter_spec.js +++ b/test/spec/modules/appnexusAstBidAdapter_spec.js @@ -129,6 +129,17 @@ describe('AppNexusAdapter', () => { delete REQUEST.bids[0].params.user; }); + it('should add source and verison to the tag', () => { + adapter.callBids(REQUEST); + + const request = JSON.parse(requests[0].requestBody); + expect(request.sdk).to.exist; + expect(request.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + it('attaches native params to the request', () => { REQUEST.bids[0].mediaType = 'native'; REQUEST.bids[0].nativeParams = { diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js new file mode 100644 index 00000000000..f11ef18a35a --- /dev/null +++ b/test/spec/modules/currency_spec.js @@ -0,0 +1,248 @@ + +import { + getCurrencyRates +} from 'test/fixtures/fixtures'; + +import { + setConfig, + addBidResponseDecorator, + + currencySupportEnabled, + currencyRates +} from 'modules/currency'; + +var assert = require('chai').assert; +var expect = require('chai').expect; + +describe('currency', function () { + describe('setConfig', () => { + it('results in currencySupportEnabled = false when currency not configured', () => { + setConfig({}); + expect(currencySupportEnabled).to.equal(false); + }); + + it('results in currencySupportEnabled = true and currencyRates being loaded when configured', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'JPY' }); + fakeCurrencyFileServer.respond(); + expect(currencyRates.dataAsOf).to.equal('2017-04-25'); + expect(currencySupportEnabled).to.equal(true); + }); + }); + + describe('bidder override', () => { + it('allows setConfig to set bidder currency', () => { + setConfig({}); + + var bid = { cpm: 1, bidder: 'rubicon' }; + var innerBid; + + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + + setConfig({ + adServerCurrency: 'GBP', + bidderCurrencyDefault: { + rubicon: 'GBP' + } + }); + + wrappedAddBidResponseFn('elementId', bid); + + expect(innerBid.currency).to.equal('GBP') + }); + + it('uses adapter currency over currency override if specified', () => { + setConfig({}); + + var bid = { cpm: 1, currency: 'JPY', bidder: 'rubicon' }; + var innerBid; + + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + + setConfig({ + adServerCurrency: 'JPY', + bidderCurrencyDefault: { + rubicon: 'GBP' + } + }); + + wrappedAddBidResponseFn('elementId', bid); + + expect(innerBid.currency).to.equal('JPY') + }); + + it('uses rates specified in json when provided', () => { + setConfig({ + adServerCurrency: 'USD', + rates: { + USD: { + JPY: 100 + } + } + }); + + var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; + var innerBid; + + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + + wrappedAddBidResponseFn('elementId', bid); + + expect(innerBid.cpm).to.equal('1.0000'); + }); + }); + + describe('currency.addBidResponseDecorator bidResponseQueue', () => { + it('not run until currency rates file is loaded', () => { + setConfig({}); + + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + + var marker = false; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + marker = true; + }); + var bid = { 'cpm': 1, 'currency': 'USD' }; + + setConfig({ 'adServerCurrency': 'JPY' }); + wrappedAddBidResponseFn('elementId', bid); + expect(marker).to.equal(false); + + fakeCurrencyFileServer.respond(); + expect(marker).to.equal(true); + }); + }); + + describe('currency.addBidResponseDecorator', () => { + it('should leave bid at 1 when currency support is not enabled and fromCurrency is USD', () => { + setConfig({}); + var bid = { 'cpm': 1, 'currency': 'USD' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal(1); + }); + + it('should result in NO_BID when currency support is not enabled and fromCurrency is not USD', () => { + setConfig({}); + var bid = { 'cpm': 1, 'currency': 'GBP' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }); + + it('should not buffer bid when currency is already in desired currency', () => { + setConfig({ + 'adServerCurrency': 'USD' + }); + var bid = { 'cpm': 1, 'currency': 'USD' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(bid).to.equal(innerBid); + }); + + it('should result in NO_BID when fromCurrency is not supported in file', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'JPY' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'ABC' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }); + + it('should result in NO_BID when adServerCurrency is not supported in file', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'ABC' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'GBP' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }); + + it('should return 1 when currency support is enabled and same currency code is requested as is set to adServerCurrency', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'JPY' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'JPY' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal(1); + expect(innerBid.currency).to.equal('JPY'); + }); + + it('should return direct conversion rate when fromCurrency is one of the configured bases', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'GBP' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'USD' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal('0.7798'); + expect(innerBid.currency).to.equal('GBP'); + }); + + it('should return reciprocal conversion rate when adServerCurrency is one of the configured bases, but fromCurrency is not', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'GBP' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'CNY' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal('0.1133'); + expect(innerBid.currency).to.equal('GBP'); + }); + + it('should return intermediate conversion rate when neither fromCurrency nor adServerCurrency is one of the configured bases', () => { + var fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ 'adServerCurrency': 'CNY' }); + fakeCurrencyFileServer.respond(); + var bid = { 'cpm': 1, 'currency': 'JPY' }; + var innerBid; + var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + innerBid = bid; + }); + wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal('0.0623'); + expect(innerBid.currency).to.equal('CNY'); + }); + }); +}); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js new file mode 100644 index 00000000000..c781406da6e --- /dev/null +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -0,0 +1,599 @@ +describe('improvedigital adapter tests', function () { + const expect = require('chai').expect; + const Adapter = require('modules/improvedigitalBidAdapter'); + const bidmanager = require('src/bidmanager'); + const adloader = require('src/adloader'); + const constants = require('src/constants.json'); + var bidfactory = require('src/bidfactory'); + var utils = require('src/utils.js'); + + var improveDigitalAdapter, + sandbox, + bidsRequestedOriginal; + + const simpleBidRequest = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + placementId: 1012544 + } + } + ] + }; + + const simpleSmartTagBidRequest = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + publisherId: 1032, + placementKey: 'data_team_test_hb_smoke_test' + } + } + ] + }; + + const keyValueBidRequest = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + placementId: 1012546, + keyValues: { + hbkv: ['01'] + } + } + } + ] + }; + + const sizeBidRequest = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + placementId: 1012545, + size: { + w: 800, + h: 600 + } + } + } + ] + }; + + const twoAdSlots = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + placementId: 1012544, + } + }, + { + bidId: '4d5e6f', + placementCode: 'placement2', + params: { + placementId: 1012545, + size: { + w: 800, + h: 600 + } + } + } + ] + }; + + const threeAdSlots = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + placementId: 1012544, + } + }, + { + bidId: '4d5e6f', + placementCode: 'placement2', + params: { + placementId: 1012545, + size: { + w: 800, + h: 600 + } + } + }, + { + bidId: '7g8h9i', + placementCode: 'placement3', + params: { + placementId: 1012546, + keyValues: { + hbkv: ['01'] + } + } + } + ] + }; + + const badRequest1 = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + unknownId: 123456 + } + } + ] + }; + + const twoAdSlotsSingleRequest = { + bidderCode: 'improvedigital', + bids: [ + { + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + singleRequest: true, + placementId: 1012544, + } + }, + { + bidId: '4d5e6f', + placementCode: 'placement2', + params: { + placementId: 1012545, + size: { + w: 800, + h: 600 + } + } + } + ] + }; + + const simpleResponse = { + id: '701903620', + site_id: 191642, + bid: [ + { + price: 1.85185185185185, + lid: 268514, + advid: '5279', + id: '1a2b3c', + sync: [ + 'http://link', + 'http://link2', + 'http://link3' + ], + nurl: 'http://nurl', + h: 300, + pid: 1053687, + crid: '422030', + w: 300, + cid: '99005', + adm: 'document.writeln(\" { + improveDigitalAdapter = new Adapter(); + sandbox = sinon.sandbox.create(); + sandbox.stub( + utils, + 'getUniqueIdentifierStr', + function() { + var retValue = randomNumber.toString(); + randomNumber++; + return retValue; + } + ); + bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; + $$PREBID_GLOBAL$$._bidsRequested = []; + }); + + afterEach(() => { + sandbox.restore(); + $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; + }); + + describe('callBids simpleBidRequest', () => { + beforeEach(() => { + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(simpleBidRequest); + }); + 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); + }); + }); + + describe('callBids simpleSmartTagBidRequest', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(simpleSmartTagBidRequest); + }); + 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); + }); + }); + + describe('callBids keyValueBidRequest', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(keyValueBidRequest); + }); + 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); + }); + }); + + describe('callBids sizeBidRequest', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(sizeBidRequest); + }); + 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); + }); + }); + + describe('callBids twoAdSlots', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(twoAdSlots); + }); + 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); + }); + }); + + describe('callBids threeAdSlots', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(threeAdSlots); + }); + 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); + }); + }); + + describe('callBids bad request 1', () => { + beforeEach(() => { + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(badRequest1); + }); + it('should not call loadScript', () => { + sinon.assert.notCalled(adloader.loadScript); + }); + }); + + describe('callBids twoAdSlotsSingleRequest', () => { + beforeEach(() => { + randomNumber = 9876543210; + sandbox.stub( + adloader, + 'loadScript' + ); + improveDigitalAdapter.callBids(twoAdSlotsSingleRequest); + }); + 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); + }); + }); + + describe('improveDigitalResponse no response', () => { + beforeEach(() => { + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); + improveDigitalAdapter.callBids(simpleBidRequest); + $$PREBID_GLOBAL$$.improveDigitalResponse([]); + }); + it('should not call bidmanager.addBidResponse', () => { + sinon.assert.notCalled(bidmanager.addBidResponse); + }); + }); + + describe('improveDigitalResponse simpleResponse', () => { + beforeEach(() => { + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); + improveDigitalAdapter.callBids(simpleBidRequest); + $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponse); + }); + it('should call bidmanager.addBidResponse once with correct parameters', () => { + sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); + }); + }); + + describe('improveDigitalResponse zero bid', () => { + beforeEach(() => { + randomNumber = 1111111111; + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); + improveDigitalAdapter.callBids(simpleBidRequest); + $$PREBID_GLOBAL$$.improveDigitalResponse(zeroPriceResponse); + }); + it('should call bidmanager.addBidResponse once with correct parameters', () => { + sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, statusMessage: 'Bid returned empty or error response', adId: '1a2b3c'})); + }); + }); + + describe('improveDigitalResponse multipleResponseWithOneNoBid', () => { + beforeEach(() => { + randomNumber = 1111111111; + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); + improveDigitalAdapter.callBids(twoAdSlots); + $$PREBID_GLOBAL$$.improveDigitalResponse(multipleResponseWithOneNoBid); + }); + it('should call bidmanager.addBidResponse once with correct parameters', () => { + sinon.assert.calledTwice(bidmanager.addBidResponse); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, adId: '1a2b3c', statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185})); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); + }); + }); + + describe('improveDigitalResponse multipleInvalidResponses', () => { + beforeEach(() => { + randomNumber = 1111111111; + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); + improveDigitalAdapter.callBids(twoAdSlots); + $$PREBID_GLOBAL$$.improveDigitalResponse(multipleInvalidResponses); + }); + it('should call bidmanager.addBidResponse twice both with invalid', () => { + sinon.assert.calledTwice(bidmanager.addBidResponse); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '1a2b3c', statusMessage: 'Bid returned empty or error response'})); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); + }); + }); + + describe('improveDigitalResponse simpleResponseNoSync', () => { + beforeEach(() => { + sandbox.stub( + bidmanager, + 'addBidResponse' + ); + $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); + improveDigitalAdapter.callBids(simpleBidRequest); + $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponseNoSync); + }); + it('should call bidmanager.addBidResponse once with correct parameters', () => { + sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); + }); + }); +}); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b30fa083a68..02c07ef85af 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1101,7 +1101,7 @@ describe('the rubicon adapter', () => { window.$$PREBID_GLOBAL$$.getConfig = origGetConfig; }); - it('should add the Emily iframe by default', () => { + it('should not add the Emily iframe by default', () => { sinon.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { var config = { rubicon: { userSync: {delay: 10} @@ -1116,10 +1116,10 @@ describe('the rubicon adapter', () => { clock.tick(9); let iframes = document.querySelectorAll('[src="' + emilyUrl + '"]'); expect(iframes.length).to.equal(0); - // move clock to usersync delay, iframe should have been added + // move clock to usersync delay, iframe should still not have been added clock.tick(1); iframes = document.querySelectorAll('[src="' + emilyUrl + '"]'); - expect(iframes.length).to.equal(1); + expect(iframes.length).to.equal(0); }); it('should add the Emily iframe when enabled', () => { diff --git a/test/spec/modules/serverbidBidAdapter_spec.js b/test/spec/modules/serverbidBidAdapter_spec.js index 598695caf16..dcbd644b715 100644 --- a/test/spec/modules/serverbidBidAdapter_spec.js +++ b/test/spec/modules/serverbidBidAdapter_spec.js @@ -3,7 +3,8 @@ import Adapter from 'modules/serverbidBidAdapter'; import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -const ENDPOINT = '//e.serverbid.com/api/v2'; +const ENDPOINT = 'https://e.serverbid.com/api/v2'; +const SMARTSYNC_CALLBACK = 'serverbidCallBids'; const REQUEST = { 'bidderCode': 'serverbid', @@ -37,15 +38,15 @@ const RESPONSE = { 'creativeId': 1950991, 'flightId': 2788300, 'campaignId': 542982, - 'clickUrl': 'http://e.serverbid.com/r', - 'impressionUrl': 'http://e.serverbid.com/i.gif', + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', 'contents': [{ 'type': 'html', 'body': '', 'data': { 'height': 90, 'width': 728, - 'imageUrl': 'http://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' }, 'template': 'image' @@ -136,6 +137,69 @@ describe('serverbidAdapter', () => { expect(response.cpm).to.be.above(0); }); + describe('with SMARTSYNC=true', () => { + it('registers bids when callback is called promptly by smartsync', (done) => { + window.SMARTSYNC = true; + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.callBids(REQUEST); + + setTimeout(() => { + window[SMARTSYNC_CALLBACK](); + 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'); + expect(response.cpm).to.be.above(0); + window.SMARTSYNC = false; + done(); + }, 0); + }); + + it('registers bids when callback is never called by smartsync', (done) => { + window.SMARTSYNC = true; + window.SMARTSYNC_TIMEOUT = 100; + + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.callBids(REQUEST); + setTimeout(() => { + 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'); + expect(response.cpm).to.be.above(0); + window.SMARTSYNC = false; + done(); + }, window.SMARTSYNC_TIMEOUT * 2); + }); + + it('registers bids when callback is called (but late) by smartsync', (done) => { + window.SMARTSYNC = true; + window.SMARTSYNC_TIMEOUT = 100; + + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.callBids(REQUEST); + setTimeout(() => { + window[SMARTSYNC_CALLBACK](); + 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'); + expect(response.cpm).to.be.above(0); + window.SMARTSYNC = false; + done(); + }, window.SMARTSYNC_TIMEOUT * 2); + }); + }); + it('handles nobid responses', () => { server.respondWith(JSON.stringify({ 'decisions': [] diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 070f20958bb..5453e594155 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import Adapter from '../../../modules/sharethroughBidAdapter'; import bidManager from '../../../src/bidmanager'; +import bidfactory from '../../../src/bidfactory'; describe('sharethrough adapter', () => { let adapter; @@ -69,9 +70,11 @@ describe('sharethrough adapter', () => { let firstBid; let secondBid; let server; + let stubAddBidResponse; + let stubCreateBid; beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); + stubAddBidResponse = sandbox.stub(bidManager, 'addBidResponse'); server = sinon.fakeServer.create(); $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); @@ -117,6 +120,7 @@ describe('sharethrough adapter', () => { afterEach(() => { server.restore(); + stubAddBidResponse.reset(); }); it('should add a bid object for each bid', () => { @@ -167,5 +171,70 @@ describe('sharethrough adapter', () => { expect(firstBid).to.have.property('pkey', 'aaaa1111'); expect(secondBid).to.have.property('pkey', 'bbbb2222'); }); + + describe('when bidResponse string cannot be JSON parsed', () => { + beforeEach(() => { + pbjs._bidsRequested.push(bidderRequest); + adapter.str.placementCodeSet['foo'] = {}; + + server.respondWith(/aaaa1111/, 'non JSON string'); + adapter.callBids(bidderRequest); + + server.respond(); + }); + + afterEach(() => { + server.restore(); + stubAddBidResponse.reset(); + }); + + it('should add a bid response', () => { + sinon.assert.called(bidManager.addBidResponse); + }); + + it('should set bidder code on invalid bid response', () => { + let bidResponse = bidManager.addBidResponse.firstCall.args[1] + expect(bidResponse).to.have.property('bidderCode', 'sharethrough') + }); + }); + + describe('when no fill', () => { + beforeEach(() => { + pbjs._bidsRequested.push(bidderRequest); + adapter.str.placementCodeSet['foo'] = {}; + + let bidderResponse1 = { + 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', + 'bidId': 'bidId1', + 'creatives': [ + { + 'cpm': 12.34, + 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', + 'version': 1 + } + ], + 'stxUserId': '' + }; + + server.respondWith(/aaaa1111/, JSON.stringify(bidderResponse1)); + adapter.callBids(bidderRequest); + + server.respond(); + }); + + afterEach(() => { + server.restore(); + stubAddBidResponse.reset(); + }); + + it('should add a bid response', () => { + sinon.assert.called(bidManager.addBidResponse); + }); + + it('should set bidder code on invalid bid response', () => { + let bidResponse = bidManager.addBidResponse.firstCall.args[1] + expect(bidResponse).to.have.property('bidderCode', 'sharethrough') + }); + }); }); }); diff --git a/test/spec/modules/yieldbotBidAdapter_spec.js b/test/spec/modules/yieldbotBidAdapter_spec.js index bad1368c0f0..a29d441e751 100644 --- a/test/spec/modules/yieldbotBidAdapter_spec.js +++ b/test/spec/modules/yieldbotBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import YieldbotAdapter from 'modules/yieldbotBidAdapter'; import bidManager from 'src/bidmanager'; import adLoader from 'src/adloader'; +import {cloneJson} from 'src/utils'; const bidderRequest = { bidderCode: 'yieldbot', @@ -14,7 +15,7 @@ const bidderRequest = { bidder: 'yieldbot', bidderRequestId: '187a340cb9ccc0', params: { psn: '1234', slot: 'medrec' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ece', + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec0', placementCode: '/4294967296/adunit0' }, { @@ -23,9 +24,18 @@ const bidderRequest = { bidder: 'yieldbot', bidderRequestId: '187a340cb9ccc1', params: { psn: '1234', slot: 'leaderboard' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ece', + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec1', placementCode: '/4294967296/adunit1' - } + }, + { + bidId: '2640ad280208cd', + sizes: [[300, 250]], + bidder: 'yieldbot', + bidderRequestId: '187a340cb9ccc2', + params: { psn: '1234', slot: 'medrec' }, + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec2', + placementCode: '/4294967296/adunit2' + }, ] }; @@ -38,6 +48,12 @@ const YB_BID_FIXTURE = { }, leaderboard: { ybot_ad: 'n' + }, + noop: { + ybot_ad: 'y', + ybot_slot: 'noop', + ybot_cpm: '200', + ybot_size: '300x250' } }; @@ -47,11 +63,9 @@ function createYieldbotMockLib() { pub: (psn) => {}, defineSlot: (slotName, optionalDomIdOrConfigObject, optionalTime) => {}, enableAsync: () => {}, - go: () => { window.yieldbot._initialized = true; }, + go: () => {}, nextPageview: (slots, callback) => {}, - getSlotCriteria: (slotName) => { - return YB_BID_FIXTURE[slotName] || {ybot_ad: 'n'}; - } + getSlotCriteria: (slotName) => {} }; } @@ -67,28 +81,63 @@ function mockYieldbotBidRequest() { window.ybotq = []; } +const localSetupTestRegex = /localSetupTest$/; +const MAKE_BID_REQUEST = true; let sandbox; let bidManagerStub; let yieldbotLibStub; -beforeEach(function() { - window.$$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); -}); - -function setupTest() { +/** + * Test initialization hook. Makes initial adapter and mock bid requests
+ * unless the test is a special case with "localSetupTest".
+ * 1. All suite tests are initialized with required mocks and stubs
+ * 2. If the test title does not end in "localSetupTest", adapter and + * mock bid requests are executed + * 3. Test titles ending in "localSetupTest" are special case tests and are + * expected to call setupTest(object, MAKE_BID_REQUEST) where + * applicable + * @param {object} testRequest bidder request bids fixture + * @param {boolean} force trigger adapter callBids and Yieldbot library request + * @private + */ +function setupTest(testRequest, force = false) { sandbox = sinon.sandbox.create(); createYieldbotMockLib(); sandbox.stub(adLoader, 'loadScript'); - yieldbotLibStub = sandbox.stub(window.yieldbot); - yieldbotLibStub.getSlotCriteria.restore(); + + yieldbotLibStub = {}; + yieldbotLibStub.nextPageview = sandbox.stub(window.yieldbot, 'nextPageview'); + yieldbotLibStub.defineSlot = sandbox.stub(window.yieldbot, 'defineSlot'); + yieldbotLibStub.pub = sandbox.stub(window.yieldbot, 'pub'); + yieldbotLibStub.enableAsync = sandbox.stub(window.yieldbot, 'enableAsync'); + + yieldbotLibStub.getSlotCriteria = + sandbox.stub( + window.yieldbot, + 'getSlotCriteria', + (slotName) => { + return YB_BID_FIXTURE[slotName] || {ybot_ad: 'n'}; + }); + + yieldbotLibStub.go = + sandbox.stub( + window.yieldbot, + 'go', + () => { + window.yieldbot._initialized = true; + }); bidManagerStub = sandbox.stub(bidManager, 'addBidResponse'); - const adapter = new YieldbotAdapter(); - adapter.callBids(bidderRequest); - mockYieldbotBidRequest(); + const ybAdapter = new YieldbotAdapter(); + let request = testRequest || cloneJson(bidderRequest); + if ((this && !this.currentTest.parent.title.match(localSetupTestRegex)) || force === MAKE_BID_REQUEST) { + ybAdapter.callBids(request); + mockYieldbotBidRequest(); + } + return { adapter: ybAdapter, localRequest: request }; } function restoreTest() { @@ -97,15 +146,71 @@ function restoreTest() { } describe('Yieldbot adapter tests', function() { - describe('callBids', function() { - beforeEach(function () { - setupTest(); + let adapter; + let localRequest; + beforeEach(function () { + const testSetupCtx = setupTest.call(this); + adapter = testSetupCtx.adapter; + localRequest = testSetupCtx.localRequest; + }); + + afterEach(function() { + restoreTest(); + }); + + describe('getUniqueSlotSizes', function() { + it('should return [] for string sizes', function() { + const sizes = adapter.getUniqueSlotSizes('widthxheight'); + expect(sizes).to.deep.equal([]); }); - afterEach(function() { - restoreTest(); + it('should return [] for Object sizes', function() { + const sizes = adapter.getUniqueSlotSizes({width: 300, height: 250}); + expect(sizes).to.deep.equal([]); + }); + + it('should return [] for boolean sizes', function() { + const sizes = adapter.getUniqueSlotSizes(true); + expect(sizes).to.deep.equal([]); }); + it('should return [] for undefined sizes', function() { + const sizes = adapter.getUniqueSlotSizes(undefined); + expect(sizes).to.deep.equal([]); + }); + + it('should return [] for function sizes', function() { + const sizes = adapter.getUniqueSlotSizes(function () {}); + expect(sizes).to.deep.equal([]); + }); + + it('should return [] for number sizes', function() { + const sizes = adapter.getUniqueSlotSizes(1111); + expect(sizes).to.deep.equal([]); + }); + + it('should return [] for array of numbers', function() { + const sizes = adapter.getUniqueSlotSizes([300, 250]); + expect(sizes).to.deep.equal([]); + }); + + it('should return array of unique strings', function() { + const sizes = adapter.getUniqueSlotSizes(['300x250', '300x600', '728x90', '300x250']); + expect(sizes).to.deep.equal([['300', '250'], ['300', '600'], ['728', '90']]); + }); + + it('should return array of unique strings for string elements only', function() { + const sizes = adapter.getUniqueSlotSizes(['300x250', ['threexfour']]); + expect(sizes).to.deep.equal([['300', '250']]); + }); + + it('should return array of unique strings, including non-numeric', function() { + const sizes = adapter.getUniqueSlotSizes(['300x250', 'threexfour', 'fivexsix']); + expect(sizes).to.deep.equal([['300', '250'], ['three', 'four'], ['five', 'si']]); + }); + }); + + describe('callBids', function() { it('should request the yieldbot library', function() { sinon.assert.calledOnce(adLoader.loadScript); sinon.assert.calledWith(adLoader.loadScript, '//cdn.yldbt.com/js/yieldbot.intent.js'); @@ -116,24 +221,35 @@ describe('Yieldbot adapter tests', function() { sinon.assert.calledWith(yieldbotLibStub.pub, '1234'); }); - it('should define yieldbot slots', function() { + it('should not repeat multiply defined slot sizes', 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]]}); + sinon.assert.neverCalledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [['300', '250'], ['300', '600'], ['300', '250']]}); }); - it('should not use inherited Object properties', function() { - restoreTest(); + 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 not use inherited Object properties, localSetupTest', function() { let oProto = Object.prototype; - oProto.superProp = [300, 250]; + oProto.superProp = ['300', '250']; expect(Object.prototype.superProp).to.be.an('array'); - setupTest(); + localRequest.bids.forEach((bid) => { + expect(bid.superProp).to.be.an('array'); + }); + + expect(YB_BID_FIXTURE.medrec.superProp).to.deep.equal(['300', '250']); + expect(YB_BID_FIXTURE.leaderboard.superProp).to.deep.equal(['300', '250']); - sinon.assert.neverCalledWith(yieldbotLibStub.defineSlot, 'superProp', {sizes: [300, 250]}); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [[300, 250], [300, 600]]}); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard', {sizes: [[728, 90], [970, 90]]}); + restoreTest(); + setupTest(localRequest, MAKE_BID_REQUEST); + + sinon.assert.neverCalledWith(yieldbotLibStub.defineSlot, 'superProp', {sizes: ['300', '250']}); + sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [['300', '250'], ['300', '600']]}); + sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard', {sizes: [['728', '90'], ['970', '90']]}); delete oProto.superProp; expect(Object.prototype.superProp).to.be.an('undefined'); @@ -145,7 +261,7 @@ describe('Yieldbot adapter tests', function() { it('should add bid response after yieldbot request callback', function() { const plc1 = bidManagerStub.firstCall.args[0]; - expect(plc1).to.equal(bidderRequest.bids[0].placementCode); + expect(plc1).to.equal(localRequest.bids[0].placementCode); const pb_bid1 = bidManagerStub.firstCall.args[1]; expect(pb_bid1.bidderCode).to.equal('yieldbot'); @@ -161,7 +277,7 @@ describe('Yieldbot adapter tests', function() { expect(pb_bid1.ad).to.match(/yieldbot\.renderAd\('medrec:300x250'\)/); const plc2 = bidManagerStub.secondCall.args[0]; - expect(plc2).to.equal(bidderRequest.bids[1].placementCode); + expect(plc2).to.equal(localRequest.bids[1].placementCode); const pb_bid2 = bidManagerStub.secondCall.args[1]; expect(pb_bid2.bidderCode).to.equal('yieldbot'); @@ -169,104 +285,234 @@ describe('Yieldbot adapter tests', function() { 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(); + it('should validate slot dimensions, localSetupTest', function() { + let invalidSizeBid = { + bidId: '2640ad280208ce', + sizes: [[728, 90], [300, 250], [970, 90]], + bidder: 'yieldbot', + bidderRequestId: '187a340cb9ccc3', + params: { psn: '1234', slot: 'medrec' }, + requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec3', + placementCode: '/4294967296/adunit3' + }; + + const bidResponseMedrec = { + bidderCode: 'yieldbot', + width: '300', + height: '250', + statusMessage: 'Bid available', + cpm: 2, + ybot_ad: 'y', + ybot_slot: 'medrec', + ybot_cpm: '200', + ybot_size: '300x250' + }; + + localRequest.bids = [invalidSizeBid]; + restoreTest(); + setupTest(localRequest, MAKE_BID_REQUEST); - createYieldbotMockLib(); + let bidManagerFirstCall = bidManagerStub.firstCall; - sandbox.stub(adLoader, 'loadScript'); - yieldbotLibStub = sandbox.stub(window.yieldbot); - yieldbotLibStub.getSlotCriteria.restore(); - yieldbotLibStub.go.restore(); - bidManagerStub = sandbox.stub(bidManager, 'addBidResponse'); + expect(bidManagerFirstCall.args[0]).to.equal('/4294967296/adunit3'); + expect(bidManagerFirstCall.args[1]).to.include(bidResponseMedrec); }); - afterEach(function() { - sandbox.restore(); - restoreYieldbotMockLib(); + it('should make slot bid available once only', function() { + const bidResponseMedrec = { + bidderCode: 'yieldbot', + width: '300', + height: '250', + statusMessage: 'Bid available', + cpm: 2, + ybot_ad: 'y', + ybot_slot: 'medrec', + ybot_cpm: '200', + ybot_size: '300x250' + }; + + const bidResponseNone = { + bidderCode: 'yieldbot', + width: 0, + height: 0, + statusMessage: 'Bid returned empty or error response' + }; + + let firstCall = bidManagerStub.firstCall; + let secondCall = bidManagerStub.secondCall; + let thirdCall = bidManagerStub.thirdCall; + + expect(firstCall.args[0]).to.equal('/4294967296/adunit0'); + expect(firstCall.args[1]).to.include(bidResponseMedrec); + + expect(secondCall.args[0]).to.equal('/4294967296/adunit1'); + expect(secondCall.args[1]).to.include(bidResponseNone); + + expect(thirdCall.args[0]).to.equal('/4294967296/adunit2'); + expect(thirdCall.args[1]).to.include(bidResponseNone); }); + }); + describe('callBids, refresh', function() { it('should use yieldbot.nextPageview after first callBids', function() { - const adapter = new YieldbotAdapter(); - adapter.callBids(bidderRequest); - mockYieldbotBidRequest(); - expect(window.yieldbot._initialized).to.equal(true); - adapter.callBids(bidderRequest); + adapter.callBids(localRequest); mockYieldbotBidRequest(); sinon.assert.calledOnce(yieldbotLibStub.nextPageview); }); it('should call yieldbot.nextPageview with slot config of requested bids', function() { - window.$$PREBID_GLOBAL$$._bidsRequested = window.$$PREBID_GLOBAL$$._bidsRequested.filter(o => { - return o.bidderCode !== 'yieldbot'; - }); - - const adapter = new YieldbotAdapter(); - adapter.callBids(bidderRequest); - mockYieldbotBidRequest(); - expect(window.yieldbot._initialized).to.equal(true); sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec'); sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard'); - window.$$PREBID_GLOBAL$$._bidsRequested = window.$$PREBID_GLOBAL$$._bidsRequested.filter(o => { - return o.bidderCode !== 'yieldbot'; - }); - - const refreshBids = bidderRequest.bids.filter((object) => { return object.placementCode === '/4294967296/adunit1'; }); - let refreshRequest = Object.assign({}, bidderRequest); + const refreshBids = localRequest.bids.filter((object) => { return object.placementCode === '/4294967296/adunit1'; }); + let refreshRequest = cloneJson(localRequest); refreshRequest.bids = refreshBids; expect(refreshRequest.bids.length).to.equal(1); adapter.callBids(refreshRequest); mockYieldbotBidRequest(); - const bid = refreshBids[0]; - const expectedSlots = { 'leaderboard': [[728, 90], [970, 90]] }; + const expectedSlots = { 'leaderboard': [['728', '90'], ['970', '90']] }; sinon.assert.calledWithExactly(yieldbotLibStub.nextPageview, expectedSlots); }); - it('should not throw on callBids without bidsRequested', function() { - const adapter = new YieldbotAdapter(); - adapter.callBids(bidderRequest); + it('should not repeat multiply defined slot sizes', function() { + // placementCode: '/4294967296/adunit0' + // placementCode: '/4294967296/adunit2' + // Both placements declare medrec:300x250 + adapter.callBids(localRequest); mockYieldbotBidRequest(); + sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + const expectedSlots = { 'leaderboard': [['728', '90'], ['970', '90']], 'medrec': [['300', '250'], ['300', '600']]}; + sinon.assert.calledWithExactly(yieldbotLibStub.nextPageview, expectedSlots); + }); + + it('should not add empty bidResponse on callBids without bidsRequested', function() { expect(window.yieldbot._initialized).to.equal(true); + expect(bidManagerStub.calledThrice).to.equal(true); + + adapter.callBids({}); + mockYieldbotBidRequest(); + + expect(bidManagerStub.calledThrice).to.equal(true); // the initial bids + sinon.assert.notCalled(yieldbotLibStub.nextPageview); + }); - window.$$PREBID_GLOBAL$$._bidsRequested = window.$$PREBID_GLOBAL$$._bidsRequested.filter(o => { - return o.bidderCode !== 'yieldbot'; + it('should validate slot dimensions', function() { + localRequest.bids.map(bid => { + bid.sizes = [[640, 480], [1024, 768]]; }); - adapter.callBids(bidderRequest); + const bidResponseNone = { + bidderCode: 'yieldbot', + width: 0, + height: 0, + statusMessage: 'Bid returned empty or error response' + }; + + adapter.callBids(localRequest); mockYieldbotBidRequest(); - sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + + expect(bidManagerStub.getCalls().length).to.equal(6); + + let lastNextPageview = yieldbotLibStub.nextPageview.lastCall; + let nextPageviewSlots = lastNextPageview.args[0]; + expect(nextPageviewSlots.medrec).to.deep.equal([['640', '480'], ['1024', '768']]); + expect(nextPageviewSlots.leaderboard).to.deep.equal([['640', '480'], ['1024', '768']]); + + let fourthCall = bidManagerStub.getCall(3); + let fifthCall = bidManagerStub.getCall(4); + let sixthCall = bidManagerStub.getCall(5); + + expect(fourthCall.args[0]).to.equal('/4294967296/adunit0'); + expect(fourthCall.args[1]).to.include(bidResponseNone); + + expect(fifthCall.args[0]).to.equal('/4294967296/adunit1'); + expect(fifthCall.args[1]).to.include(bidResponseNone); + + expect(sixthCall.args[0]).to.equal('/4294967296/adunit2'); + expect(sixthCall.args[1]).to.include(bidResponseNone); }); - it('should not add empty bidResponse on callBids without bidsRequested', function() { - window.$$PREBID_GLOBAL$$._bidsRequested = window.$$PREBID_GLOBAL$$._bidsRequested.filter(o => { - return o.bidderCode !== 'yieldbot'; + it('should not make requests for previously requested bids', function() { + const bidResponseMedrec = { + bidderCode: 'yieldbot', + width: '300', + height: '250', + statusMessage: 'Bid available', + cpm: 2, + ybot_ad: 'y', + ybot_slot: 'medrec', + ybot_cpm: '200', + ybot_size: '300x250' + }; + + const bidResponseNone = { + bidderCode: 'yieldbot', + width: 0, + height: 0, + statusMessage: 'Bid returned empty or error response' + }; + + // Refresh #1 + adapter.callBids(localRequest); + mockYieldbotBidRequest(); + + expect(bidManagerStub.getCalls().length).to.equal(6); + + let lastNextPageview = yieldbotLibStub.nextPageview.lastCall; + let nextPageviewSlots = lastNextPageview.args[0]; + expect(nextPageviewSlots.medrec).to.deep.equal([['300', '250'], ['300', '600']]); + expect(nextPageviewSlots.leaderboard).to.deep.equal([['728', '90'], ['970', '90']]); + + let fourthCall = bidManagerStub.getCall(3); + let fifthCall = bidManagerStub.getCall(4); + let sixthCall = bidManagerStub.getCall(5); + + expect(fourthCall.args[0]).to.equal('/4294967296/adunit0'); + expect(fourthCall.args[1]).to.include(bidResponseMedrec); + + expect(fifthCall.args[0]).to.equal('/4294967296/adunit1'); + expect(fifthCall.args[1]).to.include(bidResponseNone); + + expect(sixthCall.args[0]).to.equal('/4294967296/adunit2'); + expect(sixthCall.args[1]).to.include(bidResponseNone); + + localRequest.bids.map(bid => { + bid.sizes = [[640, 480], [1024, 768]]; }); + let bidForNinethCall = localRequest.bids[localRequest.bids.length - 1]; + bidForNinethCall.sizes = [[300, 250]]; - const adapter = new YieldbotAdapter(); - adapter.callBids(bidderRequest); + // Refresh #2 + adapter.callBids(localRequest); mockYieldbotBidRequest(); - let bidResponses = window.$$PREBID_GLOBAL$$._bidsReceived.filter(o => { - return o.bidderCode === 'yieldbot'; - }); + expect(bidManagerStub.getCalls().length).to.equal(9); - expect(bidResponses.length).to.equal(0); + lastNextPageview = yieldbotLibStub.nextPageview.lastCall; + nextPageviewSlots = lastNextPageview.args[0]; + expect(nextPageviewSlots.medrec).to.deep.equal([['640', '480'], ['1024', '768'], ['300', '250']]); + expect(nextPageviewSlots.leaderboard).to.deep.equal([['640', '480'], ['1024', '768']]); - adapter.callBids(bidderRequest); - mockYieldbotBidRequest(); - sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + let seventhCall = bidManagerStub.getCall(6); + let eighthCall = bidManagerStub.getCall(7); + let ninethCall = bidManagerStub.getCall(8); + + expect(seventhCall.args[0]).to.equal('/4294967296/adunit0'); + expect(seventhCall.args[1]).to.include(bidResponseNone); + + expect(eighthCall.args[0]).to.equal('/4294967296/adunit1'); + expect(eighthCall.args[1]).to.include(bidResponseNone); + + expect(ninethCall.args[0]).to.equal('/4294967296/adunit2'); + expect(ninethCall.args[1]).to.include(bidResponseMedrec); }); }); }); diff --git a/test/spec/unit/bidmanager_spec.js b/test/spec/unit/bidmanager_spec.js index f70c9906440..d51a0ab8a8f 100644 --- a/test/spec/unit/bidmanager_spec.js +++ b/test/spec/unit/bidmanager_spec.js @@ -59,7 +59,7 @@ describe('The Bid Manager', () => { expect(bid.cpm).to.equal(adjustCpm(0.1)); if (usePayloadResponse) { - expect(bid.vastPayload).to.equal(''); + expect(bid.vastXml).to.equal(''); if (usePrebidCache) { expect(bid.vastUrl).to.equal(`https://prebid.adnxs.com/pbc/v1/cache?uuid=FAKE_UUID`); } @@ -176,7 +176,7 @@ describe('The Bid Manager', () => { it('should add bids with a vastUrl and then execute the callbacks signaling the end of the auction', testAddVideoBid(true, true, stubProvider, false)); - it('should add bids with a vastPayload and then execute the callbacks signaling the end of the auction', + it('should add bids with a vastXml and then execute the callbacks signaling the end of the auction', testAddVideoBid(true, true, stubProvider, true)); it('should gracefully do nothing when adUnitCode is undefined', () => { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index df8c070df97..ba322f23a1d 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -339,6 +339,94 @@ describe('Unit: Prebid Module', function () { }); }); + describe('getAdserverTargeting', function() { + const customConfigObject = { + 'buckets': [ + { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 }, + { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05}, + { 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 }, + { 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 } + ] + }; + let currentPriceBucket; + let bid; + + before(() => { + resetAuction(); + currentPriceBucket = configObj.getConfig('priceGranularity'); + configObj.setConfig({ priceGranularity: customConfigObject }); + bid = Object.assign({}, + bidfactory.createBid(2), + getBidResponses()[5] + ); + }); + + after(() => { + configObj.setConfig({ priceGranularity: currentPriceBucket }); + resetAuction(); + }) + + beforeEach(() => { + $$PREBID_GLOBAL$$._bidsReceived = []; + }) + + it('should get correct hb_pb when using bid.cpm is between 0 to 5', () => { + bid.cpm = 2.1234; + bidmanager.addBidResponse(bid.adUnitCode, bid); + var expected = { + '/19968336/header-bid-tag-0': { + hb_adid: '275bd666f5a5a5d', + hb_bidder: 'brealtime', + hb_pb: '2.12', + hb_size: '300x250' + } + } + expect($$PREBID_GLOBAL$$.getAdserverTargeting()).to.deep.equal(expected); + }); + + it('should get correct hb_pb when using bid.cpm is between 5 to 8', () => { + bid.cpm = 6.78; + bidmanager.addBidResponse(bid.adUnitCode, bid); + var expected = { + '/19968336/header-bid-tag-0': { + hb_adid: '275bd666f5a5a5d', + hb_bidder: 'brealtime', + hb_pb: '6.75', + hb_size: '300x250' + } + } + expect($$PREBID_GLOBAL$$.getAdserverTargeting()).to.deep.equal(expected); + }); + + it('should get correct hb_pb when using bid.cpm is between 8 to 20', () => { + bid.cpm = 19.5234; + bidmanager.addBidResponse(bid.adUnitCode, bid); + var expected = { + '/19968336/header-bid-tag-0': { + hb_adid: '275bd666f5a5a5d', + hb_bidder: 'brealtime', + hb_pb: '19.50', + hb_size: '300x250' + } + } + expect($$PREBID_GLOBAL$$.getAdserverTargeting()).to.deep.equal(expected); + }); + + it('should get correct hb_pb when using bid.cpm is between 20 to 25', () => { + bid.cpm = 21.5234; + bidmanager.addBidResponse(bid.adUnitCode, bid); + var expected = { + '/19968336/header-bid-tag-0': { + hb_adid: '275bd666f5a5a5d', + hb_bidder: 'brealtime', + hb_pb: '21.00', + hb_size: '300x250' + } + } + expect($$PREBID_GLOBAL$$.getAdserverTargeting()).to.deep.equal(expected); + }); + }); + describe('getBidResponses', function () { it('should return expected bid responses when not passed an adunitCode', function () { var result = $$PREBID_GLOBAL$$.getBidResponses(); @@ -1300,7 +1388,7 @@ describe('Unit: Prebid Module', function () { let priceGranularity = configObj.getConfig('priceGranularity'); let newCustomPriceBucket = configObj.getConfig('customPriceBucket'); expect(goodConfig).to.deep.equal(newCustomPriceBucket); - expect(priceGranularity).to.equal(CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + expect(priceGranularity).to.equal(CONSTANTS.GRANULARITY_OPTIONS.CUSTOM); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 2bbdcbaf6e6..e8cb041ebf2 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -524,4 +524,34 @@ describe('Utils', function () { assert.equal(arr.length, count, 'Polyfill test fails') }); }); + + describe('deepAccess', function() { + var obj = { + 1: 2, + test: { + first: 11 + } + }; + + it('should allow deep access of object properties', function() { + var value1 = utils.deepAccess(obj, 'test'); + assert.deepEqual(value1, obj.test); + + var value2 = utils.deepAccess(obj, 'test.first'); + assert.equal(value2, 11); + + var value3 = utils.deepAccess(obj, 1); + assert.equal(value3, 2); + }); + + it('should allow safe access (returning undefined for missing properties and not throwing exceptions)', function() { + var value; + + assert.doesNotThrow(function() { + value = utils.deepAccess(obj, 'test.second.third'); + }); + + assert.equal(value, undefined); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 75118855405..e9af314218e 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -92,9 +92,9 @@ describe('The video cache', () => { assertRequestMade({ vastUrl: 'my-mock-url.com' }, expectedValue) }); - it('should make the expected request when store() is called on an ad with a vastPayload', () => { - const vastPaload = ''; - assertRequestMade({ vastPayload: vastPaload }, vastPaload); + it('should make the expected request when store() is called on an ad with vastXml', () => { + const vastXml = ''; + assertRequestMade({ vastXml: vastXml }, vastXml); }); function assertRequestMade(bid, expectedValue) { diff --git a/yarn.lock b/yarn.lock index d14d5c36a6e..949c467070e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,7 +95,7 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.1.5, ajv@^5.2.0: +ajv@^5.0.0, ajv@^5.1.5, ajv@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" dependencies: @@ -1743,7 +1743,7 @@ content-type@~1.0.1, content-type@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" -convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.5.0: +convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -4133,14 +4133,14 @@ istanbul-api@^1.1.8: mkdirp "^0.5.1" once "^1.4.0" -istanbul-instrumenter-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-2.0.0.tgz#e5492900ab0bba835efa8024cb00be9b3eea2700" +istanbul-instrumenter-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.0.tgz#9f553923b22360bac95e617aaba01add1f7db0b2" dependencies: - convert-source-map "^1.3.0" - istanbul-lib-instrument "^1.1.3" - loader-utils "^0.2.16" - object-assign "^4.1.0" + convert-source-map "^1.5.0" + istanbul-lib-instrument "^1.7.3" + loader-utils "^1.1.0" + schema-utils "^0.3.0" istanbul-lib-coverage@^1.1.1: version "1.1.1" @@ -4152,7 +4152,19 @@ istanbul-lib-hook@^1.0.7: dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.1.3, istanbul-lib-instrument@^1.7.4: +istanbul-lib-instrument@^1.7.3: + version "1.7.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.5.tgz#adb596f8f0cb8b95e739206351a38a586af21b1e" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.17.4" + istanbul-lib-coverage "^1.1.1" + semver "^5.3.0" + +istanbul-lib-instrument@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.4.tgz#e9fd920e4767f3d19edc765e2d6b3f5ccbd0eea8" dependencies: @@ -4618,7 +4630,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5, loader-utils@~0.2.2, loader-utils@~0.2.3, loader-utils@~0.2.5: +loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@~0.2.2, loader-utils@~0.2.3, loader-utils@~0.2.5: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: @@ -6599,6 +6611,12 @@ saucelabs@^1.4.0: dependencies: https-proxy-agent "^1.0.0" +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + dependencies: + ajv "^5.0.0" + "semver@2 || 3 || 4 || 5", semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"