diff --git a/.travis.yml b/.travis.yml index 73b5de5ae4a..364d47edf4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: required + dist: trusty language: node_js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 2e5c798e218..f1ec912fd26 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -116,7 +116,7 @@ }, { bidder: 'adform', - // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' ] + // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test', priceType ] params: { adxDomain: 'adx.adform.net', //optional mid: 158989, diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js new file mode 100644 index 00000000000..6dba55f88a7 --- /dev/null +++ b/modules/adformBidAdapter.js @@ -0,0 +1,107 @@ +'use strict'; + +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'adform'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [], + isBidRequestValid: function (bid) { + return !!(bid.params.mid); + }, + buildRequests: function (validBidRequests) { + var i, l, j, k, bid, _key, _value, reqParams; + var request = []; + var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ]; + var netRevenue = 'net'; + var bids = JSON.parse(JSON.stringify(validBidRequests)); + for (i = 0, l = bids.length; i < l; i++) { + bid = bids[i]; + if (bid.params.priceType === 'gross') { + netRevenue = 'gross'; + } + for (j = 0, k = globalParams.length; j < k; j++) { + _key = globalParams[j][0]; + _value = bid[_key] || bid.params[_key]; + if (_value) { + bid[_key] = bid.params[_key] = null; + globalParams[j][1] = _value; + } + } + reqParams = bid.params; + reqParams.transactionId = bid.transactionId; + request.push(formRequestUrl(reqParams)); + } + + request.unshift('//' + globalParams[0][1] + '/adx/?rp=4'); + + request.push('stid=' + validBidRequests[0].requestId); + + for (i = 1, l = globalParams.length; i < l; i++) { + _key = globalParams[i][0]; + _value = globalParams[i][1]; + if (_value) { + request.push(_key + '=' + encodeURIComponent(_value)); + } + } + + return { + method: 'GET', + url: request.join('&'), + bids: validBidRequests, + netRevenue: netRevenue, + bidder: 'adform' + }; + + function formRequestUrl(reqData) { + var key; + var url = []; + + for (key in reqData) { + if (reqData.hasOwnProperty(key) && reqData[key]) { url.push(key, '=', reqData[key], '&'); } + } + + return encodeURIComponent(btoa(url.join('').slice(0, -1))); + } + }, + interpretResponse: function (serverResponse, bidRequest) { + var bidObject, response, bid; + var bidRespones = []; + var bids = bidRequest.bids; + var responses = serverResponse.body; + for (var i = 0; i < responses.length; i++) { + response = responses[i]; + bid = bids[i]; + if (response.response === 'banner' && verifySize(response, bid.sizes)) { + bidObject = { + requestId: bid.bidId, + cpm: response.win_bid, + width: response.width, + height: response.height, + creativeId: bid.bidId, + dealId: response.deal_id, + currency: response.win_cur, + netRevenue: bidRequest.netRevenue !== 'gross', + ttl: 360, + ad: response.banner, + bidderCode: bidRequest.bidder, + transactionId: bid.transactionId + }; + bidRespones.push(bidObject); + } + } + + return bidRespones; + + function verifySize(adItem, validSizes) { + for (var j = 0, k = validSizes.length; j < k; j++) { + if (adItem.width === validSizes[j][0] && + adItem.height === validSizes[j][1]) { + return true; + } + } + return false; + } + } +}; +registerBidder(spec); diff --git a/modules/adformBidAdapter.md b/modules/adformBidAdapter.md new file mode 100644 index 00000000000..9f357b21729 --- /dev/null +++ b/modules/adformBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +Module Name: Adform Bidder Adapter +Module Type: Bidder Adapter +Maintainer: Scope.FL.Scripts@adform.com + +# Description + +Module that connects to Adform demand sources to fetch bids. +Banner formats are supported. + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], // a display size + bids: [ + { + bidder: "adform", + params: { + adxDomain: 'adx.adform.net', //optional + mid: '292063', + priceType: 'gross' // default is 'net' + } + } + ] + }, + ]; +``` diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js new file mode 100644 index 00000000000..6851a7d3bd5 --- /dev/null +++ b/modules/admixerBidAdapter.js @@ -0,0 +1,74 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'admixer'; +const ENDPOINT_URL = '//inv-nets.admixer.net/prebid.1.0.aspx'; +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: ['banner', 'video'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!bid.params.zone; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {bidderRequest} - bidderRequest.bids[] is an array of AdUnits and bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidderRequest) { + const payload = { + imps: [], + referrer: utils.getTopWindowUrl(), + }; + bidderRequest.forEach((bid) => { + if (bid.bidder === BIDDER_CODE) { + payload.imps.push(bid); + } + }); + const payloadString = JSON.stringify(payload); + return { + method: 'GET', + url: ENDPOINT_URL, + data: `data=${payloadString}`, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + // loop through serverResponses { + try { + serverResponse = serverResponse.body; + serverResponse.forEach((bidResponse) => { + const bidResp = { + requestId: bidResponse.bidId, + cpm: bidResponse.cpm, + width: bidResponse.width, + height: bidResponse.height, + ad: bidResponse.ad, + ttl: bidResponse.ttl, + creativeId: bidResponse.creativeId, + netRevenue: bidResponse.netRevenue, + currency: bidResponse.currency, + vastUrl: bidResponse.vastUrl, + }; + bidResponses.push(bidResp); + }); + } catch (e) { + utils.logError(e); + } + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/admixerBidAdapter.md b/modules/admixerBidAdapter.md new file mode 100644 index 00000000000..682f5629115 --- /dev/null +++ b/modules/admixerBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +Module Name: Admixer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@admixer.net + +# Description + +Connects to Admixer demand source to fetch bids. +Banner and Video formats are supported. +Please use ```admixer``` as the bidder code. + +# Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "admixer", + params: { + zone: '2eb6bd58-865c-47ce-af7f-a918108c3fd2' + } + } + ] + },{ + code: 'mobile-banner-ad-div', + sizes: [[300, 50]], // a mobile size + bids: [ + { + bidder: "admixer", + params: { + zone: '62211486-c50b-4356-9f0f-411778d31fcc' + } + } + ] + },{ + code: 'video-ad', + sizes: [[300, 50]], + mediaType: 'video', + bids: [ + { + bidder: "admixer", + params: { + zone: 'ebeb1e79-8cb4-4473-b2d0-2e24b7ff47fd' + } + } + ] + }, + ]; +``` diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 64e3ae14835..30ef9c7dd90 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,6 +1,7 @@ import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; +import { logInfo } from 'src/utils'; import find from 'core-js/library/fn/array/find'; import findIndex from 'core-js/library/fn/array/find-index'; @@ -12,19 +13,14 @@ const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE; const bidWon = CONSTANTS.EVENTS.BID_WON; const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT; -let bidwonTimeout = 1000; - let adomikAdapter = Object.assign(adapter({}), { // Track every event needed track({ eventType, args }) { switch (eventType) { case auctionInit: + adomikAdapter.initializeBucketEvents() adomikAdapter.currentContext.id = args.auctionId - adomikAdapter.currentContext.timeout = args.timeout - if (args.config.bidwonTimeout !== undefined && typeof args.config.bidwonTimeout === 'number') { - bidwonTimeout = args.config.bidwonTimeout; - } break; case bidTimeout: @@ -39,12 +35,9 @@ let adomikAdapter = Object.assign(adapter({}), break; case bidWon: - adomikAdapter.bucketEvents.push({ - type: 'winner', - event: { - id: args.adId, - placementCode: args.adUnitCode - } + adomikAdapter.sendWonEvent({ + id: args.adId, + placementCode: args.adUnitCode }); break; @@ -61,24 +54,25 @@ let adomikAdapter = Object.assign(adapter({}), break; case auctionEnd: - setTimeout(() => { - if (adomikAdapter.bucketEvents.length > 0) { - adomikAdapter.sendTypedEvent(); - } - }, bidwonTimeout); + if (adomikAdapter.bucketEvents.length > 0) { + adomikAdapter.sendTypedEvent(); + } break; } } } ); +adomikAdapter.initializeBucketEvents = function() { + adomikAdapter.bucketEvents = []; +} + adomikAdapter.sendTypedEvent = function() { const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, - timeout: adomikAdapter.currentContext.timeout, hostname: window.location.hostname, eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { let sizes = []; @@ -108,8 +102,11 @@ adomikAdapter.sendTypedEvent = function() { }) }; + const stringBulkEvents = JSON.stringify(bulkEvents) + logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); + // Encode object in base64 - const encodedBuf = window.btoa(JSON.stringify(bulkEvents)); + const encodedBuf = window.btoa(stringBulkEvents); // Create final url and split it in 1600 characters max (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); @@ -122,6 +119,17 @@ adomikAdapter.sendTypedEvent = function() { }) }; +adomikAdapter.sendWonEvent = function (wonEvent) { + const stringWonEvent = JSON.stringify(wonEvent) + logInfo('Won event sent to adomik prebid analytic ' + wonEvent); + + // Encode object in base64 + const encodedBuf = window.btoa(stringWonEvent); + const encodedUri = encodeURIComponent(encodedBuf); + const img = new Image(1, 1); + img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true` +} + adomikAdapter.buildBidResponse = function (bid) { return { bidder: bid.bidderCode.toUpperCase(), @@ -181,23 +189,20 @@ adomikAdapter.buildTypedEvents = function () { return groupedTypedEvents; } -// Initialize adomik object -adomikAdapter.currentContext = {}; -adomikAdapter.bucketEvents = []; - adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; adomikAdapter.enableAnalytics = function (config) { + adomikAdapter.currentContext = {}; + const initOptions = config.options; if (initOptions) { adomikAdapter.currentContext = { uid: initOptions.id, url: initOptions.url, - debug: initOptions.debug, id: '', timeouted: false, - timeout: 0, } + logInfo('Adomik Analytics enabled with config', initOptions); adomikAdapter.adapterEnableAnalytics(config); } }; diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 71b1e4f7902..9b4aa26e1a1 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -39,7 +39,8 @@ const MP_SERVER_MAP = { as: 'adserver-as.adtech.advertising.com' }; const NEXAGE_SERVER = 'hb.nexage.com'; -const BID_RESPONSE_TTL = 300; +const ONE_DISPLAY_TTL = 60; +const ONE_MOBILE_TTL = 3600; $$PREBID_GLOBAL$$.aolGlobals = { pixelsDropped: false @@ -224,7 +225,7 @@ function _parseBidResponse(response, bidRequest) { currency: response.cur, dealId: bidData.dealid, netRevenue: true, - ttl: BID_RESPONSE_TTL + ttl: bidRequest.ttl }; } @@ -274,14 +275,16 @@ function formatBidRequest(endpointCode, bid) { case AOL_ENDPOINTS.DISPLAY.GET: bidRequest = { url: _buildMarketplaceUrl(bid), - method: 'GET' + method: 'GET', + ttl: ONE_DISPLAY_TTL }; break; case AOL_ENDPOINTS.MOBILE.GET: bidRequest = { url: _buildOneMobileGetUrl(bid), - method: 'GET' + method: 'GET', + ttl: ONE_MOBILE_TTL }; break; @@ -289,6 +292,7 @@ function formatBidRequest(endpointCode, bid) { bidRequest = { url: _buildOneMobileBaseUrl(bid), method: 'POST', + ttl: ONE_MOBILE_TTL, data: bid.params, options: { contentType: 'application/json', diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index d46be776b59..21af777bdc5 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -30,7 +30,7 @@ const SOURCE = 'pbjs'; export const spec = { code: BIDDER_CODE, - aliases: ['appnexusAst', 'brealtime', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia'], + aliases: ['appnexusAst', 'brealtime', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia', 'districtm'], supportedMediaTypes: [VIDEO, NATIVE], /** @@ -216,20 +216,24 @@ function newBid(serverBid, rtbBid) { body: nativeAd.desc, cta: nativeAd.ctatext, sponsoredBy: nativeAd.sponsored, - image: { - url: nativeAd.main_img && nativeAd.main_img.url, - height: nativeAd.main_img && nativeAd.main_img.height, - width: nativeAd.main_img && nativeAd.main_img.width, - }, - icon: { - url: nativeAd.icon && nativeAd.icon.url, - height: nativeAd.icon && nativeAd.icon.height, - width: nativeAd.icon && nativeAd.icon.width, - }, clickUrl: nativeAd.link.url, clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } } else { Object.assign(bid, { width: rtbBid.rtb.banner.width, diff --git a/modules/atomxBidAdapter.js b/modules/atomxBidAdapter.js new file mode 100644 index 00000000000..f946841dffc --- /dev/null +++ b/modules/atomxBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'atomx'; + +function getDomain() { + var domain = ''; + + try { + if ((domain === '') && (window.top == window)) { + domain = window.location.href; + } + + if ((domain === '') && (window.top == window.parent)) { + domain = document.referrer; + } + + if (domain == '') { + var atomxt = 'atomxtest'; + + // It should be impossible to change the window.location.ancestorOrigins. + window.location.ancestorOrigins[0] = atomxt; + if (window.location.ancestorOrigins[0] != atomxt) { + var ancestorOrigins = window.location.ancestorOrigins; + + // If the length is 0 we are a javascript tag running in the main domain. + // But window.top != window or window.location.hostname is empty. + if (ancestorOrigins.length == 0) { + // This browser is so fucked up, just return an empty string. + return ''; + } + + // ancestorOrigins is an array where [0] is our own window.location + // and [length-1] is the top window.location. + domain = ancestorOrigins[ancestorOrigins.length - 1]; + } + } + } catch (unused) { + } + + if (domain === '') { + domain = document.referrer; + } + + if (domain === '') { + domain = window.location.href; + } + + return domain.substr(0, 512); +} + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return bid.params && (!!bid.params.id); + }, + + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + return { + method: 'GET', + url: location.protocol + '//p.ato.mx/placement', + data: { + v: 12, + id: bidRequest.params.id, + size: utils.parseSizesInput(bidRequest.sizes)[0], + prebid: bidRequest.bidId, + b: 0, + h: '7t3y9', + type: 'javascript', + screen: window.screen.width + 'x' + window.screen.height + 'x' + window.screen.colorDepth, + timezone: new Date().getTimezoneOffset(), + domain: getDomain(), + r: document.referrer.substr(0, 512), + }, + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const body = serverResponse.body; + const res = { + requestId: body.code, + cpm: body.cpm * 1000, + width: body.width, + height: body.height, + creativeId: body.creative_id, + currency: 'USD', + netRevenue: true, + ttl: 60, + }; + + if (body.adm) { + res.ad = body.adm; + } else { + res.adUrl = body.url; + } + + return [res]; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + return []; + }, +}; +registerBidder(spec); diff --git a/modules/atomxBidAdapter.md b/modules/atomxBidAdapter.md new file mode 100644 index 00000000000..7f32b12fdfe --- /dev/null +++ b/modules/atomxBidAdapter.md @@ -0,0 +1,25 @@ +# Overview +Module Name: Atomx Bidder Adapter Module +Type: Bidder Adapter +Maintainer: erik@atomx.com + +# Description +Atomx Bidder Adapter for Prebid.js. + +# Test Parameters +``` +var adUnits = [ +{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'atomx', + params: { + id: 4025860, + } + } + ] +} +]; +``` diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js new file mode 100644 index 00000000000..dfc5f514cf3 --- /dev/null +++ b/modules/eplanningBidAdapter.js @@ -0,0 +1,156 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'eplanning'; +const rnd = Math.random(); +const DEFAULT_SV = 'ads.us.e-planning.net'; +const DEFAULT_ISV = 'i.e-planning.net'; +const PARAMS = ['ci', 'sv', 't']; +const DOLLARS = 'USD'; +const NET_REVENUE = true; +const TTL = 120; +const NULL_SIZE = '1x1'; +const FILE = 'file'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return Boolean(bid.params.ci) || Boolean(bid.params.t); + }, + buildRequests: function(bidRequests) { + const method = 'GET'; + const dfpClientId = '1'; + const sec = 'ROS'; + let url; + let params; + const urlConfig = getUrlConfig(bidRequests); + + if (urlConfig.t) { + url = urlConfig.isv + '/layers/t_pbjs_2.json'; + params = {}; + } else { + url = '//' + (urlConfig.sv || DEFAULT_SV) + '/hb/1/' + urlConfig.ci + '/' + dfpClientId + '/' + (utils.getTopWindowLocation().hostname || FILE) + '/' + sec; + const referrerUrl = utils.getTopWindowReferrer(); + const spacesString = getSpacesString(bidRequests); + params = { + rnd: rnd, + e: spacesString, + ur: utils.getTopWindowUrl() || FILE, + r: 'pbjs', + pbv: '$prebid.version$', + ncb: '1' + }; + if (referrerUrl) { + params.fr = referrerUrl; + } + } + + return { + method: method, + url: url, + data: params, + adUnitToBidId: getBidIdMap(bidRequests), + }; + }, + interpretResponse: function(serverResponse, request) { + const response = serverResponse.body; + let bidResponses = []; + + if (response && !utils.isEmpty(response.sp)) { + response.sp.forEach(space => { + if (!utils.isEmpty(space.a)) { + space.a.forEach(ad => { + const bidResponse = { + requestId: request.adUnitToBidId[space.k], + cpm: ad.pr, + width: ad.w, + height: ad.h, + ad: ad.adm, + ttl: TTL, + creativeId: ad.crid, + netRevenue: NET_REVENUE, + currency: DOLLARS, + }; + bidResponses.push(bidResponse); + }); + } + }); + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + const response = !utils.isEmpty(serverResponses) && serverResponses[0].body; + + if (response && !utils.isEmpty(response.cs)) { + const responseSyncs = response.cs; + responseSyncs.forEach(sync => { + if (typeof sync === 'string' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: sync, + }); + } else if (typeof sync === 'object' && sync.ifr && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: sync.u, + }) + } + }); + } + + return syncs; + }, +} + +function cleanName(name) { + return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)/g, '_').replace(/^_+|_+$/g, ''); +} +function getUrlConfig(bidRequests) { + if (isTestRequest(bidRequests)) { + return getTestConfig(bidRequests.filter(br => br.params.t)); + } + + let config = {}; + bidRequests.forEach(bid => { + PARAMS.forEach(param => { + if (bid.params[param] && !config[param]) { + config[param] = bid.params[param]; + } + }); + }); + + if (config.sv) { + config.sv = '//' + config.sv; + } + + return config; +} +function isTestRequest(bidRequests) { + let isTest = false; + bidRequests.forEach(bid => isTest = bid.params.t); + return isTest; +} +function getTestConfig(bidRequests) { + let isv; + bidRequests.forEach(br => isv = isv || br.params.isv); + return { + t: true, + isv: '//' + (isv || DEFAULT_ISV) + }; +} +function getSpacesString(bids) { + const spacesString = bids.map(bid => + cleanName(bid.adUnitCode) + ':' + (bid.sizes && bid.sizes.length ? utils.parseSizesInput(bid.sizes).join(',') : NULL_SIZE) + ).join('+'); + + return spacesString; +} +function getBidIdMap(bidRequests) { + let map = {}; + bidRequests.forEach(bid => map[cleanName(bid.adUnitCode)] = bid.bidId); + return map; +} + +registerBidder(spec); diff --git a/modules/eplanningBidAdapter.md b/modules/eplanningBidAdapter.md new file mode 100644 index 00000000000..b6cfbb535b6 --- /dev/null +++ b/modules/eplanningBidAdapter.md @@ -0,0 +1,25 @@ +# Overview + +``` +Module Name: E-Planning Bid Adapter +Module Type: Bidder Adapter +Maintainer: ainsua@e-planning.net +``` + +# Description + +Connects to E-Planning exchange for bids. + +# Test Parameters +``` +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250]], + bids: [{ + bidder: 'eplanning', + params: { + t: 1 + } + }] +}]; +``` diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js new file mode 100644 index 00000000000..3fbc7d772fa --- /dev/null +++ b/modules/gjirafaBidAdapter.js @@ -0,0 +1,91 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'gjirafa'; +const ENDPOINT_URL = 'https://gjc.gjirafa.com/Home/GetBid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return bid.params && (!!bid.params.placementId || (!!bid.params.minCPM && !!bid.params.minCPC)); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + let gjid = Math.floor(Math.random() * 99999999); + let sizes = generateSizeParam(bidRequest.sizes); + let configId = bidRequest.params.placementId || ''; + let minCPM = bidRequest.params.minCPM || 0.0; + let minCPC = bidRequest.params.minCPC || 0.0; + let allowExplicit = bidRequest.params.explicit || 0; + const body = { + gjid: gjid, + sizes: sizes, + configId: configId, + minCPM: minCPM, + minCPC: minCPC, + allowExplicit: allowExplicit, + referrer: utils.getTopWindowUrl(), + requestid: bidRequest.bidderRequestId, + bidid: bidRequest.bidId + }; + if (document.referrer) { + body.referrer = document.referrer; + } + return { + method: 'GET', + url: ENDPOINT_URL, + data: body + }; + }); + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + const bidResponse = { + requestId: bidRequest.data.bidid, + cpm: serverBody.CPM, + width: serverBody.Width, + height: serverBody.Height, + creativeId: serverBody.CreativeId, + currency: serverBody.Currency, + netRevenue: serverBody.NetRevenue, + ttl: serverBody.TTL, + referrer: serverBody.Referrer, + ad: serverBody.Ad + }; + bidResponses.push(bidResponse); + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +registerBidder(spec); diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md new file mode 100644 index 00000000000..1ec8222d8de --- /dev/null +++ b/modules/gjirafaBidAdapter.md @@ -0,0 +1,36 @@ +# Overview +Module Name: Gjirafa Bidder Adapter Module +Type: Bidder Adapter +Maintainer: agonq@gjirafa.com + +# Description +Gjirafa Bidder Adapter for Prebid.js. + +# Test Parameters +var adUnits = [ +{ + code: 'test-div', + sizes: [[728, 90]], // leaderboard + bids: [ + { + bidder: 'gjirafa', + params: { + placementId: '71-3' + } + } + ] +},{ + code: 'test-div', + sizes: [[300, 250]], // mobile rectangle + bids: [ + { + bidder: 'gjirafa', + params: { + minCPM: 0.0001, + minCPC: 0.001, + explicit: true + } + } + ] +} +]; \ No newline at end of file diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js new file mode 100644 index 00000000000..2627b7417f5 --- /dev/null +++ b/modules/gumgumBidAdapter.js @@ -0,0 +1,170 @@ +import * as utils from 'src/utils' + +import { config } from 'src/config' +import { registerBidder } from 'src/adapters/bidderFactory' + +const BIDDER_CODE = 'gumgum' +const ALIAS_BIDDER_CODE = ['gg'] +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const DT_CREDENTIALS = { member: 'YcXr87z2lpbB' } +const TIME_TO_LIVE = 60 +let browserParams = {}; + +// TODO: potential 0 values for browserParams sent to ad server +function _getBrowserParams() { + let topWindow + let topScreen + if (browserParams.vw) { + // we've already initialized browserParams, just return it. + return browserParams + } + + try { + topWindow = global.top; + topScreen = topWindow.screen; + } catch (error) { + utils.logError(error); + return browserParams + } + + browserParams = { + vw: topWindow.innerWidth, + vh: topWindow.innerHeight, + sw: topScreen.width, + sh: topScreen.height, + pu: utils.getTopWindowUrl(), + ce: utils.cookiesAreEnabled(), + dpr: topWindow.devicePixelRatio || 1 + } + return browserParams +} + +function getWrapperCode(wrapper, data) { + return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) +} + +// TODO: use getConfig() +function _getDigiTrustQueryParams() { + function getDigiTrustId () { + var digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(DT_CREDENTIALS) : {}; + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || ''; + }; + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return {}; + } + return { + 'dt': digiTrustId.id + }; +} + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid (bid) { + const { + params, + adUnitCode + } = bid; + + switch (true) { + case !!(params.inScreen): break; + case !!(params.inSlot): break; + default: + utils.logWarn(`[GumGum] No product selected for the placement ${adUnitCode}, please check your implementation.`); + return false; + } + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests (validBidRequests) { + const bids = []; + utils._each(validBidRequests, bidRequest => { + const timeout = config.getConfig('bidderTimeout'); + const { + bidId, + params = {}, + transactionId + } = bidRequest; + const data = {} + + if (params.inScreen) { + data.t = params.inScreen; + data.pi = 2; + } + if (params.inSlot) { + data.si = parseInt(params.inSlot, 10); + data.pi = 3; + } + + bids.push({ + id: bidId, + tmax: timeout, + tId: transactionId, + pi: data.pi, + sizes: bidRequest.sizes, + url: BID_ENDPOINT, + method: 'GET', + data: Object.assign(data, _getBrowserParams(), _getDigiTrustQueryParams()) + }) + }); + return bids; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse (serverResponse, bidRequest) { + const bidResponses = [] + const serverResponseBody = serverResponse.body + const { + ad: { + price: cpm, + id: creativeId, + markup + }, + cw: wrapper + } = serverResponseBody + let isTestUnit = (bidRequest.data && bidRequest.data.pi === 3 && bidRequest.data.si === 9) + let [width, height] = utils.parseSizesInput(bidRequest.sizes)[0].split('x') + + if (creativeId) { + bidResponses.push({ + // dealId: DEAL_ID, + // referrer: REFERER, + ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, + cpm: isTestUnit ? 0.1 : cpm, + creativeId, + currency: 'USD', + height, + netRevenue: true, + requestId: bidRequest.id, + ttl: TIME_TO_LIVE, + width + }) + } + return bidResponses +} + +export const spec = { + code: BIDDER_CODE, + aliases: ALIAS_BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse +} +registerBidder(spec) diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md new file mode 100644 index 00000000000..500c2a49e3b --- /dev/null +++ b/modules/gumgumBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: GumGum Bidder Adapter +Module Type: Bidder Adapter +Maintainer: engineering@gumgum.com +``` + +# Description + +GumGum adapter for Prebid.js 1.0 + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'gumgum', + params: { + inSlot: '9' // GumGum Slot ID given to the client + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: 'gumgum', + params: { + inScreen: 'ggumtest' // GumGum Zone ID given to the client + } + } + ] + } +]; +``` diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js new file mode 100644 index 00000000000..7247a236e71 --- /dev/null +++ b/modules/mantisBidAdapter.js @@ -0,0 +1,206 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; + +function inIframe() { + try { + return window.self !== window.top && !window.mantis_link; + } catch (e) { + return true; + } +} + +function isDesktop(ignoreTouch) { + var supportsTouch = !ignoreTouch && ('ontouchstart' in window || navigator.msMaxTouchPoints); + if (inIframe()) { + return !supportsTouch; + } + var width = window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth; + return !supportsTouch && (!width || width >= (window.mantis_breakpoint || 768)); +} + +function storeUuid(uuid) { + if (window.mantis_uuid) { + return false; + } + window.mantis_uuid = uuid; + if (window.localStorage) { + try { + window.localStorage.setItem('mantis:uuid', uuid); + } catch (ex) { + } + } +} + +function isSendable(val) { + if (val === null || val === undefined) { + return false; + } + if (typeof val === 'string') { + return !(!val || /^\s*$/.test(val)); + } + if (typeof val === 'number') { + return !isNaN(val); + } + return true; +} + +function isObject(value) { + return Object.prototype.toString.call(value) === '[object Object]'; +} + +function isAmp() { + return typeof window.context === 'object' && (window.context.tagName === 'AMP-AD' || window.context.tagName === 'AMP-EMBED'); +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +function isArray(value) { + return Object.prototype.toString.call(value) === '[object Array]'; +} + +function jsonToQuery(data, chain, form) { + if (!data) { + return null; + } + var parts = form || []; + for (var key in data) { + var queryKey = key; + if (chain) { + queryKey = chain + '[' + key + ']'; + } + var val = data[key]; + if (isArray(val)) { + for (var index = 0; index < val.length; index++) { + var akey = queryKey + '[' + index + ']'; + var aval = val[index]; + if (isObject(aval)) { + jsonToQuery(aval, akey, parts); + } else if (isSendable(aval)) { + parts.push(akey + '=' + encodeURIComponent(aval)); + } + } + } else if (isObject(val) && val != data) { + jsonToQuery(val, queryKey, parts); + } else if (isSendable(val)) { + parts.push(queryKey + '=' + encodeURIComponent(val)); + } + } + return parts.join('&'); +} + +function buildMantisUrl(path, data, domain) { + var params = { + referrer: document.referrer, + tz: new Date().getTimezoneOffset(), + buster: new Date().getTime(), + secure: isSecure() + }; + if (!inIframe() || isAmp()) { + params.mobile = !isAmp() && isDesktop(true) ? 'false' : 'true'; + } + if (window.mantis_uuid) { + params.uuid = window.mantis_uuid; + } else if (window.localStorage) { + var localUuid = window.localStorage.getItem('mantis:uuid'); + if (localUuid) { + params.uuid = localUuid; + } + } + if (!inIframe()) { + try { + params.title = window.top.document.title; + params.referrer = window.top.document.referrer; + params.url = window.top.document.location.href; + } catch (ex) { + } + } else { + params.iframe = true; + } + if (isAmp()) { + params.amp = true; + if (!params.url && window.context.canonicalUrl) { + params.url = window.context.canonicalUrl; + } + if (!params.url && window.context.location) { + params.url = window.context.location.href; + } + if (!params.referrer && window.context.referrer) { + params.referrer = window.context.referrer; + } + } + Object.keys(data || {}).forEach(function (key) { + params[key] = data[key]; + }); + var query = jsonToQuery(params); + return (window.mantis_domain === undefined ? domain || 'https://mantodea.mantisadnetwork.com' : window.mantis_domain) + path + '?' + query; +} + +const spec = { + code: 'mantis', + supportedMediaTypes: ['banner', 'video', 'native'], + isBidRequestValid: function (bid) { + return !!(bid.params.property && (bid.params.code || bid.params.zoneId || bid.params.zone)); + }, + buildRequests: function (validBidRequests) { + var property = null; + validBidRequests.some(function (bid) { + if (bid.params.property) { + property = bid.params.property; + return true; + } + }); + const query = { + bids: validBidRequests.map(function (bid) { + return { + bidId: bid.bidId, + config: bid.params, + sizes: bid.sizes.map(function (size) { + return {width: size[0], height: size[1]}; + }) + }; + }), + property: property, + version: 2 + }; + return { + method: 'GET', + url: buildMantisUrl('/prebid/display', query) + '&foo', + data: '' + }; + }, + interpretResponse: function (serverResponse) { + storeUuid(serverResponse.uuid); + return serverResponse.body.ads.map(function (ad) { + return { + requestId: ad.bid, + cpm: ad.cpm, + width: ad.width, + height: ad.height, + ad: ad.html, + ttl: 86400, + creativeId: ad.view, + netRevenue: true, + currency: 'USD' + }; + }); + }, + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: buildMantisUrl('/prebid/iframe') + }]; + } + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: buildMantisUrl('/prebid/pixel') + }]; + } + } +}; + +export {spec}; + +registerBidder(spec); diff --git a/modules/mantisBidAdapter.md b/modules/mantisBidAdapter.md new file mode 100644 index 00000000000..d8896c71514 --- /dev/null +++ b/modules/mantisBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +Module Name: MANTIS Ad Network Bid Adapter + +Module Type: Bidder Adapter + +Maintainer: paris@mantisadnetwork.com + +# Description + +Module that connects to MANTIS's demand sources + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test', + sizes: [[300, 250]], + bids: [ + { + bidder: 'mantis', + params: { + property: 'demo', + zone: 'zone' + } + } + ] + } + ]; +``` diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js new file mode 100644 index 00000000000..c0b3fff3c2a --- /dev/null +++ b/modules/medianetBidAdapter.js @@ -0,0 +1,161 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +const BIDDER_CODE = 'medianet'; +const BID_URL = 'https://prebid.media.net/rtb/prebid'; + +function siteDetails(site) { + site = site || {}; + + return { + domain: site.domain || utils.getTopWindowLocation().host, + page: site.page || utils.getTopWindowUrl(), + ref: site.ref || utils.getTopWindowReferrer() + } +} + +function filterUrlsByType(urls, type) { + return urls.filter(url => url.type === type); +} + +function transformSizes(sizes) { + if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + return [getSize(sizes)]; + } + + return sizes.map(size => getSize(size)) +} + +function getSize(size) { + return { + w: parseInt(size[0], 10), + h: parseInt(size[1], 10) + } +} + +function configuredParams(params) { + return { + customer_id: params.cid + } +} + +function slotParams(bidRequest) { + // check with Media.net Account manager for bid floor and crid parameters + let params = { + id: bidRequest.bidId, + ext: { + dfp_id: bidRequest.adUnitCode + }, + banner: transformSizes(bidRequest.sizes) + }; + + if (bidRequest.params.crid) { + params.tagid = bidRequest.params.crid.toString(); + } + + let bidFloor = parseFloat(bidRequest.params.bidfloor); + if (bidFloor) { + params.bidfloor = bidFloor; + } + return params; +} + +function generatePayload(bidRequests) { + return { + site: siteDetails(bidRequests[0].params.site), + ext: configuredParams(bidRequests[0].params), + id: bidRequests[0].auctionId, + imp: bidRequests.map(request => slotParams(request)) + } +} + +function isValidBid(bid) { + return bid.no_bid === false && parseFloat(bid.cpm) > 0.0; +} + +function fetchCookieSyncUrls(response) { + if (!utils.isEmpty(response) && response[0].body && + response[0].body.ext && utils.isArray(response[0].body.ext.csUrl)) { + return response[0].body.ext.csUrl; + } + + return []; +} + +export const spec = { + + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid (if cid is present), and false otherwise. + */ + isBidRequestValid: function(bid) { + if (!bid.params) { + utils.logError(`${BIDDER_CODE} : Missing bid parameters`); + return false; + } + + if (!bid.params.cid || !utils.isStr(bid.params.cid) || utils.isEmptyStr(bid.params.cid)) { + utils.logError(`${BIDDER_CODE} : cid should be a string`); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests) { + let payload = generatePayload(bidRequests); + + return { + method: 'POST', + url: BID_URL, + data: JSON.stringify(payload) + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, request) { + let validBids = []; + + if (!serverResponse || !serverResponse.body) { + utils.logInfo(`${BIDDER_CODE} : response is empty`); + return validBids; + } + + let bids = serverResponse.body.bidList; + if (!utils.isArray(bids) || bids.length === 0) { + utils.logInfo(`${BIDDER_CODE} : no bids`); + return validBids; + } + validBids = bids.filter(bid => isValidBid(bid)); + + return validBids; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + let cookieSyncUrls = fetchCookieSyncUrls(serverResponses); + + if (syncOptions.iframeEnabled) { + return filterUrlsByType(cookieSyncUrls, 'iframe'); + } + + if (syncOptions.pixelEnabled) { + return filterUrlsByType(cookieSyncUrls, 'image'); + } + } +}; +registerBidder(spec); diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md new file mode 100644 index 00000000000..2edb120033e --- /dev/null +++ b/modules/medianetBidAdapter.md @@ -0,0 +1,59 @@ +# Overview + +``` +Module Name: media.net Bid Adapter +Module Type: Bidder Adapter +Maintainer: vedant.s@media.net +``` + +# Description + +Connects to Media.net's exchange for bids. +This adapter currently only supports Banner Ads. + +# Sample Ad Unit: For Publishers +```javascript +var adUnits = [{ + code: 'media.net-hb-ad-123456-1', + sizes: [ + [300, 250], + [300, 600], + ], + bids: [{ + bidder: 'medianet', + params: { + cid: '', + bidfloor: '', + crid: '' + } + }] +}]; +``` + +# Ad Unit and Setup: For Testing + +```html + + + +``` \ No newline at end of file diff --git a/modules/nasmediaAdmixerBidAdapter.js b/modules/nasmediaAdmixerBidAdapter.js new file mode 100644 index 00000000000..e4892f3d0ed --- /dev/null +++ b/modules/nasmediaAdmixerBidAdapter.js @@ -0,0 +1,81 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const ADMIXER_ENDPOINT = 'https://adn.admixer.co.kr:10443/prebid'; +const DEFAULT_BID_TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_REVENUE = false; + +export const spec = { + code: 'nasmediaAdmixer', + + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.ax_key); + }, + + buildRequests: function (validBidRequests) { + return validBidRequests.map(bid => { + let adSize = getSize(bid.sizes); + + return { + method: 'GET', + url: ADMIXER_ENDPOINT, + data: { + ax_key: utils.getBidIdParameter('ax_key', bid.params), + req_id: bid.bidId, + width: adSize.width, + height: adSize.height, + referrer: utils.getTopWindowUrl(), + os: getOsType() + } + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + + if (serverBody && serverBody.error_code === 0 && serverBody.body && serverBody.body.length > 0) { + let bidData = serverBody.body[0]; + + const bidResponse = { + ad: bidData.ad, + requestId: serverBody.req_id, + creativeId: bidData.ad_id, + cpm: bidData.cpm, + width: bidData.width, + height: bidData.height, + currency: bidData.currency ? bidData.currency : DEFAULT_CURRENCY, + netRevenue: DEFAULT_REVENUE, + ttl: DEFAULT_BID_TTL + }; + + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +function getOsType() { + let ua = navigator.userAgent.toLowerCase(); + let os = ['android', 'ios', 'mac', 'linux', 'window', 'etc']; + let regexp_os = [/android/i, /iphone|ipad/i, /mac/i, /linux/i, /window/i, '']; + + return regexp_os.some((tos, idx) => { + if (ua.match(tos)) { + return os[idx]; + } + }); +} + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [width, height] = parsedSizes.length ? parsedSizes[0].split('x') : []; + + return { + width: parseInt(width, 10), + height: parseInt(height, 10) + }; +} +registerBidder(spec); diff --git a/modules/nasmediaAdmixerBidAdapter.md b/modules/nasmediaAdmixerBidAdapter.md new file mode 100644 index 00000000000..aa72b7967f8 --- /dev/null +++ b/modules/nasmediaAdmixerBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: NasmeidaAdmixer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@nasmedia.co.kr +``` + +# Description + +Module that connects to NasmediaAdmixer demand sources. +Banner formats are supported. +The NasmediaAdmixer adapter doesn't support multiple sizes per ad-unit and will use the first one if multiple sizes are defined. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[320, 480]], // banner size + bids: [ + { + bidder: 'nasmediaAdmixer', + params: { + ax_key: 'ajj7jba3', //required parameter + } + } + ] + } + ]; +``` diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 051f9ace2de..9ed4f1250f8 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -30,15 +30,21 @@ export const spec = { let requests = []; let bannerRequests = []; let videoRequests = []; - let bannerBids = []; - let videoBids = []; - bids.forEach(function (bid) { - if (bid.mediaType === VIDEO) { - videoBids.push(bid); - } else { - bannerBids.push(bid); + const {bannerBids, videoBids} = bids.reduce(function(acc, curBid) { + // Fallback to banner ads if nothing specified + if (!curBid.mediaTypes || utils.isEmpty(curBid.mediaTypes)) { + if (curBid.mediaType && curBid.mediaType == VIDEO) { + acc.videoBids.push(curBid); + } else { + acc.bannerBids.push(curBid); + } + } else if (curBid.mediaTypes.video) { + acc.videoBids.push(curBid); + } else if (curBid.mediaTypes.banner) { + acc.bannerBids.push(curBid); } - }); + return acc; + }, {bannerBids: [], videoBids: []}); // build banner requests if (bannerBids.length !== 0) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 1f04c2fe466..9e9d3ebfa7a 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -16,13 +16,13 @@ Module that connects to OpenX's demand sources { code: 'test-div', sizes: [[728, 90]], // a display size - mediaType: 'banner', + mediaTypes: {'banner': {}}, bids: [ { - bidder: "openx", + bidder: 'openx', params: { - unit: "539439964", - delDomain: "se-demo-d.openx.net" + unit: '539439964', + delDomain: 'se-demo-d.openx.net' } } ] @@ -30,7 +30,7 @@ Module that connects to OpenX's demand sources { code: 'video1', sizes: [[640,480]], - mediaType: 'video', + mediaTypes: {'video': {}}, bids: [ { bidder: 'openx', diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 8e7cc5075b2..6b4b5e7d4e6 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -11,6 +11,61 @@ function RhythmOneBidAdapter() { return true; }; + this.getUserSyncs = function (syncOptions) { + let slots = []; + let placementIds = []; + + for (let k in slotsToBids) { + slots.push(k); + placementIds.push(getFirstParam('placementId', [slotsToBids[k]])); + } + + let data = { + doc_version: 1, + doc_type: 'Prebid Audit', + placement_id: placementIds.join(',').replace(/[,]+/g, ',').replace(/^,|,$/g, '') + }; + let w = typeof (window) !== 'undefined' ? window : {document: {location: {href: ''}}}; + let ao = w.document.location.ancestorOrigins; + let q = []; + let u = '//hbevents.1rx.io/audit?'; + + if (ao && ao.length > 0) { + data.ancestor_origins = ao[ao.length - 1]; + } + + data.popped = w.opener !== null ? 1 : 0; + data.framed = w.top === w ? 0 : 1; + + try { + data.url = w.top.document.location.href.toString(); + } catch (ex) { + data.url = w.document.location.href.toString(); + } + + try { + data.prebid_version = '$prebid.version$'; + data.prebid_timeout = config.getConfig('bidderTimeout'); + } catch (ex) { } + + data.response_ms = Date.now() - loadStart; + data.placement_codes = slots.join(','); + data.bidder_version = version; + + for (let k in data) { + q.push(encodeURIComponent(k) + '=' + encodeURIComponent((typeof data[k] === 'object' ? JSON.stringify(data[k]) : data[k]))); + } + + q.sort(); + + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: u + q.join('&') + }]; + } + }; + function getFirstParam(key, validBidRequests) { for (let i = 0; i < validBidRequests.length; i++) { if (validBidRequests[i].params && validBidRequests[i].params[key]) { @@ -22,6 +77,7 @@ function RhythmOneBidAdapter() { let slotsToBids = {}; let that = this; let version = '1.0.0.0'; + let loadStart = Date.now(); this.buildRequests = function (BRs) { let fallbackPlacementId = getFirstParam('placementId', BRs); @@ -29,13 +85,14 @@ function RhythmOneBidAdapter() { return []; } + loadStart = Date.now(); slotsToBids = {}; let query = []; let w = (typeof window !== 'undefined' ? window : {}); - function p(k, v) { - if (v instanceof Array) { v = v.join(','); } + function p(k, v, d) { + if (v instanceof Array) { v = v.join((d || ',')); } if (typeof v !== 'undefined') { query.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)); } } @@ -98,8 +155,7 @@ function RhythmOneBidAdapter() { if ((new w.ActiveXObject('ShockwaveFlash.ShockwaveFlash'))) { return 1; } - } catch (e) { - } + } catch (e) { } } return 0; @@ -186,12 +242,14 @@ function RhythmOneBidAdapter() { if (bidRequest.mediaTypes && bidRequest.mediaTypes.video) { bidResponse.vastUrl = bid.nurl; + bidResponse.mediaType = 'video'; bidResponse.ttl = 10000; } else { bidResponse.ad = bid.adm; } bids.push(bidResponse); } + return bids; }; } diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js index 6e866c6547e..6e4ae34bd76 100644 --- a/modules/sekindoUMBidAdapter.js +++ b/modules/sekindoUMBidAdapter.js @@ -47,6 +47,8 @@ export const spec = { queryString = utils.tryAppendQueryString(queryString, 'hbcb', '1');/// legasy queryString = utils.tryAppendQueryString(queryString, 'dcpmflr', bidfloor); queryString = utils.tryAppendQueryString(queryString, 'protocol', protocol); + queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.width); + queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.height); if (bidRequest.mediaType === 'video' || (typeof bidRequest.mediaTypes == 'object' && typeof bidRequest.mediaTypes.video == 'object')) { queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.playerWidth); queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.playerHeight); diff --git a/modules/sekindoUMBidAdapter.md b/modules/sekindoUMBidAdapter.md index 9f44e7a855e..24c2aaec6db 100755 --- a/modules/sekindoUMBidAdapter.md +++ b/modules/sekindoUMBidAdapter.md @@ -19,6 +19,8 @@ Banner, Outstream and Native formats are supported. bidder: 'sekindoUM', params: { spaceId: 14071 + width:300, //optional + weight:250, //optional } }] }, diff --git a/modules/serverbidBidAdapter.js b/modules/serverbidBidAdapter.js index e77d7f32a35..84cae1fa1c0 100644 --- a/modules/serverbidBidAdapter.js +++ b/modules/serverbidBidAdapter.js @@ -176,6 +176,12 @@ const sizeMap = [ sizeMap[77] = '970x90'; sizeMap[123] = '970x250'; sizeMap[43] = '300x600'; +sizeMap[286] = '970x66'; +sizeMap[3230] = '970x280'; +sizeMap[429] = '486x60'; +sizeMap[374] = '700x500'; +sizeMap[934] = '300x1050'; +sizeMap[1578] = '320x100'; function getSize(sizes) { const result = []; diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md index 934362c69c4..87b51e665e2 100644 --- a/modules/serverbidBidAdapter.md +++ b/modules/serverbidBidAdapter.md @@ -35,7 +35,7 @@ Connects to Serverbid for receiving bids from configured demand sources. params: { networkId: '9969', siteId: '980639', - zoneId: '178503' + zoneIds: [178503] } } ] diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index f16b8b96ec8..ec1f0247455 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -37,11 +37,13 @@ export const spec = { const bidsMap = {}; const bids = validBidRequests || []; let priceType = 'net'; + let reqId; bids.forEach(bid => { if (bid.params.priceType === 'gross') { priceType = 'gross'; } + reqId = bid.bidderRequestId; if (!bidsMap[bid.params.uid]) { bidsMap[bid.params.uid] = [bid]; auids.push(bid.params.uid); @@ -54,6 +56,7 @@ export const spec = { u: utils.getTopWindowUrl(), pt: priceType, auids: auids.join(','), + r: reqId }; return { diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js new file mode 100644 index 00000000000..bcfc4e246ac --- /dev/null +++ b/modules/viBidAdapter.js @@ -0,0 +1,69 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const BIDDER_CODE = 'vi'; +const SUPPORTED_MEDIA_TYPES = [BANNER]; + +function isBidRequestValid(bid) { + return !!(bid.params.pubId); +} + +function buildRequests(bidReqs) { + let imps = []; + utils._each(bidReqs, function (bid) { + imps.push({ + id: bid.bidId, + sizes: utils.parseSizesInput(bid.sizes).map(size => size.split('x')), + bidFloor: parseFloat(bid.params.bidFloor) > 0 ? bid.params.bidFloor : 0 + }); + }); + + const bidRequest = { + id: bidReqs[0].requestId, + imps: imps, + publisherId: utils.getBidIdParameter('pubId', bidReqs[0].params), + siteId: utils.getBidIdParameter('siteId', bidReqs[0].params), + cat: utils.getBidIdParameter('cat', bidReqs[0].params), + language: utils.getBidIdParameter('lang', bidReqs[0].params), + domain: utils.getTopWindowLocation().hostname, + page: utils.getTopWindowUrl(), + referrer: utils.getTopWindowReferrer() + }; + return { + method: 'POST', + url: `//pb.vi-serve.com/prebid/bid`, + data: JSON.stringify(bidRequest), + options: {contentType: 'application/json', withCredentials: false} + }; +} + +function interpretResponse(bids) { + let responses = []; + utils._each(bids.body, function(bid) { + responses.push({ + requestId: bid.id, + cpm: parseFloat(bid.price), + width: parseInt(bid.width, 10), + height: parseInt(bid.height, 10), + creativeId: bid.creativeId, + dealId: bid.dealId || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(`${bid.ad}`), + ttl: 60000 + }); + }); + return responses; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/viBidAdapter.md b/modules/viBidAdapter.md new file mode 100644 index 00000000000..23288024fcc --- /dev/null +++ b/modules/viBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +``` +Module Name: vi bid adapter +Module Type: Bidder adapter +Maintainer: support@vi.ai +``` + +# Description + +The video intelligence (vi) adapter integration to the Prebid library. +Connects to vi’s demand sources. +There should be only one ad unit with vi bid adapter on each single page. + +# Test Parameters + +``` +var adUnits = [{ + code: 'div-0', + sizes: [[320, 480]], + bids: [{ + bidder: 'vi', + params: { + pubId: 'sb_test', + lang: 'en-US', + cat: 'IAB1', + bidFloor: 0.05 //optional + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | +| `pubId` | required | Publisher ID, provided by vi | 'sb_test' | +| `lang` | required | Ad language, in ISO 639-1 language code format | 'en-US', 'es-ES', 'de' | +| `cat` | required | Ad IAB category (top-level or subcategory), single one supported | 'IAB1', 'IAB9-1' | +| `bidFloor` | optional | Lowest value of expected bid price | 0.001 | + diff --git a/package.json b/package.json index 661cbfc3495..b4ffaecda35 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "gulp-babel": "^6.1.2", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", - "gulp-connect": "^5.0.0", + "gulp-connect": "5.0.0", "gulp-documentation": "^3.2.1", "gulp-eslint": "^4.0.0", "gulp-footer": "^1.0.5", diff --git a/src/adaptermanager.js b/src/adaptermanager.js index c8dc72e7ddc..5577ffc6fca 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -149,6 +149,9 @@ function getAdUnitCopyForClientAdapters(adUnits) { exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) { let bidRequests = []; + + adUnits = exports.checkBidRequestSizes(adUnits); + let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { bidderCodes = shuffle(bidderCodes); @@ -211,6 +214,54 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, return bidRequests; }; +exports.checkBidRequestSizes = (adUnits) => { + Array.prototype.forEach.call(adUnits, adUnit => { + if (adUnit.sizes) { + utils.logWarn('Usage of adUnits.sizes will eventually be deprecated. Please define size dimensions within the corresponding area of the mediaTypes. (eg mediaTypes.banner.sizes).'); + } + + const mediaTypes = adUnit.mediaTypes; + if (mediaTypes && mediaTypes.banner) { + const banner = mediaTypes.banner; + if (banner.sizes) { + adUnit.sizes = banner.sizes; + } else { + utils.logError('Detected a mediaTypes.banner object did not include sizes. This is a required field for the mediaTypes.banner object. Removing invalid mediaTypes.banner object from request.'); + delete adUnit.mediaTypes.banner; + } + } + + if (mediaTypes && mediaTypes.video) { + const video = mediaTypes.video; + if (video.playerSize) { + if (Array.isArray(video.playerSize) && video.playerSize.length === 2 && Number.isInteger(video.playerSize[0]) && Number.isInteger(video.playerSize[1])) { + adUnit.sizes = video.playerSize; + } else { + utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [640, 480]. Removing invalid mediaTypes.video.playerSize property from request.'); + delete adUnit.mediaTypes.video.playerSize; + } + } + } + + if (mediaTypes && mediaTypes.native) { + const native = mediaTypes.native; + if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { + utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); + delete adUnit.mediaTypes.native.image.sizes; + } + if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) { + utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); + delete adUnit.mediaTypes.native.image.aspect_ratios; + } + if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) { + utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); + delete adUnit.mediaTypes.native.icon.sizes; + } + } + }); + return adUnits; +} + exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => { if (!bidRequests.length) { utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); diff --git a/src/auction.js b/src/auction.js index 7c6a752c057..2a4cc128c2a 100644 --- a/src/auction.js +++ b/src/auction.js @@ -147,14 +147,24 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) } function done(bidRequestId) { - var innerBidRequestId = bidRequestId; + const innerBidRequestId = bidRequestId; return delayExecution(function() { - let request = find(_bidderRequests, (bidRequest) => { + const request = find(_bidderRequests, (bidRequest) => { return innerBidRequestId === bidRequest.bidderRequestId; }); - request.doneCbCallCount += 1; - // In case of mediaType video and prebidCache enabled, call bidsBackHandler after cache is stored. - if ((request.bids.filter(videoAdUnit).length == 0) || (request.bids.filter(videoAdUnit).length > 0 && !config.getConfig('cache.url'))) { + + const nonVideoBid = request.bids.filter(videoAdUnit).length === 0; + const videoBid = request.bids.filter(videoAdUnit).length > 0; + const videoBidNoCache = videoBid && !config.getConfig('cache.url'); + const videoBidWithCache = videoBid && config.getConfig('cache.url'); + + // video bids with cache enabled need to be cached first before saying they are done + if (!videoBidWithCache) { + request.doneCbCallCount += 1; + } + + // in case of mediaType video and prebidCache enabled, call bidsBackHandler after cache is stored. + if (nonVideoBid || videoBidNoCache) { bidsBackAll() } }, 1); @@ -216,7 +226,9 @@ export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid let bidRequests = auctionInstance.getBidRequests(); let auctionId = auctionInstance.getAuctionId(); - let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequests, auctionId}); + let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode); + let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}); + if (bidResponse.mediaType === 'video') { tryAddVideoBid(bidResponse); } else { @@ -247,6 +259,8 @@ export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid if (!bid.vastUrl) { bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); } + // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll + bidRequest.doneCbCallCount += 1; addBidToAuction(bidResponse); auctionInstance.bidsBackAll(); } @@ -261,9 +275,7 @@ export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid // Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. // This should be called before addBidToAuction(). -function getPreparedBidForAuction({adUnitCode, bid, bidRequests, auctionId}) { - let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode); - +function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) { const start = bidRequest.start; let bidObject = Object.assign({}, bid, { diff --git a/src/native.js b/src/native.js index bfe4b64405d..6c8ac266471 100644 --- a/src/native.js +++ b/src/native.js @@ -85,6 +85,18 @@ export function nativeBidIsValid(bid, bidRequests) { return false; } + if (deepAccess(bid, 'native.image')) { + if (!deepAccess(bid, 'native.image.height') || !deepAccess(bid, 'native.image.width')) { + return false; + } + } + + if (deepAccess(bid, 'native.icon')) { + if (!deepAccess(bid, 'native.icon.height') || !deepAccess(bid, 'native.icon.width')) { + return false; + } + } + const requestedAssets = bidRequest.nativeParams; if (!requestedAssets) { return true; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 688afc35d9d..3429087a24f 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -4,6 +4,8 @@ import CONSTANTS from 'src/constants.json'; import { adjustBids } from 'src/auction'; import * as auctionModule from 'src/auction'; import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as store from 'src/videoCache'; import * as ajaxLib from 'src/ajax'; var assert = require('assert'); @@ -870,5 +872,32 @@ describe('auctionmanager.js', function () { assert.notEqual(addedBid2.adId, bids1[0].requestId); assert.equal(length, 1); }); + + it('should run auction after video bids have been cached', () => { + sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123}]); + sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url'); + + const bidsCopy = [Object.assign({}, bids[0], { mediaType: 'video'})]; + const bids1Copy = [Object.assign({}, bids1[0], { mediaType: 'video'})]; + + registerBidder(spec); + registerBidder(spec1); + + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bidsCopy); + + spec1.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec1.isBidRequestValid.returns(true); + spec1.interpretResponse.returns(bids1Copy); + + auction.callBids(); + + assert.equal(auction.getBidsReceived().length, 2); + assert.equal(auction.getAuctionStatus(), 'completed'); + + config.getConfig.restore(); + store.store.restore(); + }); }); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js new file mode 100644 index 00000000000..72f625d08c6 --- /dev/null +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -0,0 +1,246 @@ +import {assert, expect} from 'chai'; +import * as url from 'src/url'; +import {spec} from 'modules/adformBidAdapter'; + +describe('Adform adapter', () => { + let serverResponse, bidRequest, bidResponses; + let bids = []; + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adform', + 'params': { + 'mid': '19910113' + } + }; + + it('should return true when required params found', () => { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', () => { + bid.params = { + adxDomain: 'adx.adform.net' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }) + }); + + describe('buildRequests', () => { + it('should pass multiple bids via single request', () => { + let request = spec.buildRequests(bids); + let parsedUrl = parseUrl(request.url); + assert.lengthOf(parsedUrl.items, 3); + }); + + it('should handle global request parameters', () => { + let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url); + let query = parsedUrl.query; + + assert.equal(parsedUrl.path, '//newDomain/adx'); + assert.equal(query.tid, 45); + assert.equal(query.rp, 4); + assert.equal(query.fd, 1); + assert.equal(query.stid, '7aefb970-2045'); + assert.equal(query.url, encodeURIComponent('some// there')); + }); + + it('should set correct request method', () => { + let request = spec.buildRequests([bids[0]]); + assert.equal(request.method, 'GET'); + }); + + it('should correctly form bid items', () => { + let bidList = bids; + let request = spec.buildRequests(bidList); + let parsedUrl = parseUrl(request.url); + assert.deepEqual(parsedUrl.items, [ + { + mid: '1', + transactionId: '5f33781f-9552-4ca1' + }, + { + mid: '2', + someVar: 'someValue', + transactionId: '5f33781f-9552-4iuy', + priceType: 'gross' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + } + ]); + }); + + it('should not change original validBidRequests object', () => { + var resultBids = JSON.parse(JSON.stringify(bids[0])); + let request = spec.buildRequests([bids[0]]); + assert.deepEqual(resultBids, bids[0]); + }); + }); + + describe('interpretResponse', () => { + it('should respond with empty response when there is empty serverResponse', () => { + let result = spec.interpretResponse({ body: {}}, {}); + assert.deepEqual(result, []); + }); + it('should respond with empty response when sizes doesn\'t match', () => { + serverResponse.body[0].response = 'banner'; + serverResponse.body[0].width = 100; + serverResponse.body[0].height = 150; + + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); + + assert.equal(serverResponse.body.length, 1); + assert.equal(serverResponse.body[0].response, 'banner'); + assert.deepEqual(result, []); + }); + it('should respond with empty response when response from server is not banner', () => { + serverResponse.body[0].response = 'not banner'; + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); + + assert.deepEqual(result, []); + }); + it('should interpret server response correctly with one bid', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; + + assert.equal(result.requestId, '2a0cf4e'); + assert.equal(result.cpm, 13.9); + assert.equal(result.width, 300); + assert.equal(result.height, 250); + assert.equal(result.creativeId, '2a0cf4e'); + assert.equal(result.dealId, '123abc'); + assert.equal(result.currency, 'EUR'); + assert.equal(result.netRevenue, true); + assert.equal(result.ttl, 360); + assert.equal(result.ad, ''); + assert.equal(result.bidderCode, 'adform'); + assert.equal(result.transactionId, '5f33781f-9552-4ca1'); + }); + + it('should set correct netRevenue', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[1]]; + bidRequest.netRevenue = 'gross'; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; + + assert.equal(result.netRevenue, false); + }); + + it('should create bid response item for every requested item', () => { + let result = spec.interpretResponse(serverResponse, bidRequest); + assert.lengthOf(result, 3); + }); + }); + + beforeEach(() => { + let sizes = [[250, 300], [300, 250], [300, 600]]; + let adUnitCode = ['div-01', 'div-02', 'div-03']; + let placementCode = adUnitCode; + let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', priceType: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }]; + bids = [ + { + adUnitCode: placementCode[0], + bidId: '2a0cf4e', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[0], + adxDomain: 'newDomain', + tid: 45, + placementCode: placementCode[0], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4ca1' + }, + { + adUnitCode: placementCode[1], + bidId: '2a0cf5b', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[1], + placementCode: placementCode[1], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4iuy' + }, + { + adUnitCode: placementCode[2], + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + requestId: '7aefb970-2045', + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-7ev3' + } + ]; + serverResponse = { + body: [ + { + banner: '', + deal_id: '123abc', + height: 250, + response: 'banner', + width: 300, + win_bid: 13.9, + win_cur: 'EUR' + }, + { + banner: '', + deal_id: '123abc', + height: 300, + response: 'banner', + width: 250, + win_bid: 13.9, + win_cur: 'EUR' + }, + { + banner: '', + deal_id: '123abc', + height: 300, + response: 'banner', + width: 600, + win_bid: 10, + win_cur: 'EUR' + } + ], + headers: {} + }; + bidRequest = { + bidder: 'adform', + bids: bids, + method: 'GET', + url: 'url' + }; + }); +}); + +function parseUrl(url) { + const parts = url.split('/'); + const query = parts.pop().split('&'); + return { + path: parts.join('/'), + items: query + .filter((i) => !~i.indexOf('=')) + .map((i) => atob(decodeURIComponent(i)) + .split('&') + .reduce(toObject, {})), + query: query + .filter((i) => ~i.indexOf('=')) + .map((i) => i.replace('?', '')) + .reduce(toObject, {}) + }; +} + +function toObject(cache, string) { + const keyValue = string.split('='); + cache[keyValue[0]] = keyValue[1]; + return cache; +} diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js new file mode 100644 index 00000000000..13312e3d24e --- /dev/null +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -0,0 +1,117 @@ +import {expect} from 'chai'; +import {spec} from 'modules/admixerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'admixer'; +const ENDPOINT_URL = '//inv-nets.admixer.net/prebid.1.0.aspx'; +const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; + +describe('AdmixerAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'zone': ZONE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'zone': ZONE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should add referrer and imp to be equal bidRequest', () => { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data.substr(5)); + expect(payload.referrer).to.not.be.undefined; + expect(payload.imps[0]).to.deep.equal(bidRequests[0]); + }); + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', () => { + let response = { + body: [{ + 'currency': 'USD', + 'cpm': 6.210000, + 'ad': '
ad
', + 'width': 300, + 'height': 600, + 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', + 'ttl': 360, + 'netRevenue': false, + 'bidId': '5e4e763b6bc60b' + }] + }; + + it('should get correct bid response', () => { + const body = response.body; + let expectedResponse = [ + { + 'requestId': body[0].bidId, + 'cpm': body[0].cpm, + 'creativeId': body[0].creativeId, + 'width': body[0].width, + 'height': body[0].height, + 'ad': body[0].ad, + 'vastUrl': undefined, + 'currency': body[0].currency, + 'netRevenue': body[0].netRevenue, + 'ttl': body[0].ttl, + } + ]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles nobid responses', () => { + let response = []; + + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index 1907cfb14b8..37a5b46a8a2 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -9,6 +9,7 @@ describe('Adomik Prebid Analytic', function () { beforeEach(() => { sinon.spy(adomikAnalytics, 'track'); sinon.spy(adomikAnalytics, 'sendTypedEvent'); + sinon.spy(adomikAnalytics, 'sendWonEvent'); }); afterEach(() => { @@ -51,25 +52,21 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: '', - timeouted: false, - timeout: 0, + timeouted: false }); - // Step 1: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test', timeout: 3000}); + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: 'test-test-test', - timeouted: false, - timeout: 3000, + timeouted: false }); - // Step 2: Send bid requested event + // Step 3: Send bid requested event events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid] }); expect(adomikAnalytics.bucketEvents.length).to.equal(1); @@ -81,7 +78,7 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 3: Send bid response event + // Step 4: Send bid response event events.emit(constants.EVENTS.BID_RESPONSE, bid); expect(adomikAnalytics.bucketEvents.length).to.equal(2); @@ -102,29 +99,23 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 4: Send bid won event + // Step 5: Send bid won event events.emit(constants.EVENTS.BID_WON, bid); - expect(adomikAnalytics.bucketEvents.length).to.equal(3); - expect(adomikAnalytics.bucketEvents[2]).to.deep.equal({ - type: 'winner', - event: { - id: '1234', - placementCode: '0000', - } - }); + expect(adomikAnalytics.bucketEvents.length).to.equal(2); - // Step 5: Send bid timeout event + // Step 6: Send bid timeout event events.emit(constants.EVENTS.BID_TIMEOUT, {}); expect(adomikAnalytics.currentContext.timeouted).to.equal(true); - // Step 6: Send auction end event + // Step 7: Send auction end event var clock = sinon.useFakeTimers(); events.emit(constants.EVENTS.AUCTION_END, {}); setTimeout(function() { sinon.assert.callCount(adomikAnalytics.sendTypedEvent, 1); + sinon.assert.callCount(adomikAnalytics.sendWonEvent, 1); done(); }, 3000); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index d05f21a7be4..109c5bf2a0f 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -74,8 +74,10 @@ let getPixels = () => { }; describe('AolAdapter', () => { - const MARKETPLACE_URL = 'adserver-us.adtech.advertising.com/pubapi/3.0/'; - const NEXAGE_URL = 'hb.nexage.com/bidRequest?'; + const MARKETPLACE_URL = '//adserver-us.adtech.advertising.com/pubapi/3.0/'; + const NEXAGE_URL = '//hb.nexage.com/bidRequest?'; + const ONE_DISPLAY_TTL = 60; + const ONE_MOBILE_TTL = 3600; function createCustomBidRequest({bids, params} = {}) { var bidderRequest = getDefaultBidRequest(); @@ -98,7 +100,8 @@ describe('AolAdapter', () => { bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings; bidRequest = { bidderCode: 'test-bidder-code', - bidId: 'bid-id' + bidId: 'bid-id', + ttl: 1234 }; bidResponse = { body: getDefaultBidResponse() @@ -125,7 +128,7 @@ describe('AolAdapter', () => { currency: 'USD', dealId: 'deal-id', netRevenue: true, - ttl: 300 + ttl: bidRequest.ttl }); }); @@ -355,6 +358,13 @@ describe('AolAdapter', () => { let [request] = spec.buildRequests(bidRequest.bids); expect(request.url).to.contain('kvage=25;kvheight=3.42;kvtest=key'); }); + + it('should return request object for One Display when configuration is present', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.method).to.equal('GET'); + expect(request.ttl).to.equal(ONE_DISPLAY_TTL); + }); }); describe('One Mobile', () => { @@ -454,6 +464,7 @@ describe('AolAdapter', () => { let [request] = spec.buildRequests(bidRequest.bids); expect(request.url).to.contain(NEXAGE_URL); expect(request.method).to.equal('POST'); + expect(request.ttl).to.equal(ONE_MOBILE_TTL); expect(request.data).to.deep.equal(bidConfig); expect(request.options).to.deep.equal({ contentType: 'application/json', diff --git a/test/spec/modules/atomxBidAdapter_spec.js b/test/spec/modules/atomxBidAdapter_spec.js new file mode 100644 index 00000000000..fdbb01a1838 --- /dev/null +++ b/test/spec/modules/atomxBidAdapter_spec.js @@ -0,0 +1,119 @@ +import { expect } from 'chai'; +import { spec } from 'modules/atomxBidAdapter'; + +describe('atomxAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with id param', () => { + expect(spec.isBidRequestValid({ + bidder: 'atomx', + params: { + id: 1234, + }, + })).to.equal(true); + }); + + it('bidRequest with no id param', () => { + expect(spec.isBidRequestValid({ + bidder: 'atomx', + params: { + }, + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'atomx', + 'params': { + 'id': '123' + }, + 'adUnitCode': 'aaa', + 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + 'sizes': [300, 250], + 'bidId': '1abgs362e0x48a8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + 'bidder': 'atomx', + 'params': { + 'id': '456', + }, + 'adUnitCode': 'bbb', + 'transactionId': '193995b4-7122-4739-959b-2463282a138b', + 'sizes': [[800, 600]], + 'bidId': '22aidtbx5eabd9', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest url', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp('p\\.ato\\.mx/placement')); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.id).to.equal('123'); + expect(requests[0].data.size).to.equal('300x250'); + expect(requests[0].data.prebid).to.equal('1abgs362e0x48a8'); + expect(requests[1].data.id).to.equal('456'); + expect(requests[1].data.size).to.equal('800x600'); + expect(requests[1].data.prebid).to.equal('22aidtbx5eabd9'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://p.ato.mx/placement', + 'data': { + 'v': 12, + 'id': '123', + 'size': '300x250', + 'prebid': '22aidtbx5eabd9', + 'b': 0, + 'h': '7t3y9', + 'type': 'javascript', + 'screen': '800x600x32', + 'timezone': 0, + 'domain': 'https://example.com', + 'r': '', + } + }; + + const bidResponse = { + body: { + 'cpm': 0.00009, + 'width': 300, + 'height': 250, + 'url': 'http://atomx.com', + 'creative_id': 456, + 'code': '22aidtbx5eabd9', + }, + headers: {} + }; + + it('result is correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.equal('22aidtbx5eabd9'); + expect(result[0].cpm).to.equal(0.00009 * 1000); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(456); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].adUrl).to.equal('http://atomx.com'); + }); + }); +}); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js new file mode 100644 index 00000000000..2ec7f482edd --- /dev/null +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -0,0 +1,350 @@ +import { expect } from 'chai'; +import { spec } from 'modules/eplanningBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('E-Planning Adapter', () => { + const adapter = newBidder('spec'); + const CI = '12345'; + const ADUNIT_CODE = 'adunit-code'; + const ADUNIT_CODE2 = 'adunit-code-dos'; + const CLEAN_ADUNIT_CODE2 = 'adunitcodedos'; + const CLEAN_ADUNIT_CODE = 'adunitcode'; + const BID_ID = '123456789'; + const BID_ID2 = '987654321'; + const CPM = 1.3; + const W = '300'; + const H = '250'; + const ADM = '
This is an ad
'; + const I_ID = '7854abc56248f873'; + const CRID = '1234567890'; + const TEST_ISV = 'leles.e-planning.net'; + const validBid = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + }; + const testBid = { + 'bidder': 'eplanning', + 'params': { + 't': 1, + 'isv': TEST_ISV + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const invalidBid = { + 'bidder': 'eplanning', + 'params': { + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + }; + const response = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithTwoAdunits = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }] + }, { + 'k': CLEAN_ADUNIT_CODE2, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }, + ], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoAd = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': 'spname', + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoSpace = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when bid has ci parameter', () => { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid does not have ci parameter and is not a test bid', () => { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true when bid does not have ci parameter but is a test bid'), () => { + expect(spec.isBidRequestValid(testBid).to.equal(true)); + } + }); + + describe('buildRequests', () => { + let bidRequests = [validBid]; + + it('should create the url correctly', () => { + const url = spec.buildRequests(bidRequests).url; + expect(url).to.equal('//ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + }); + + it('should return GET method', () => { + const method = spec.buildRequests(bidRequests).method; + expect(method).to.equal('GET'); + }); + + it('should return r parameter with value pbjs', () => { + const r = spec.buildRequests(bidRequests).data.r; + expect(r).to.equal('pbjs'); + }); + + it('should return pbv parameter with value prebid version', () => { + const pbv = spec.buildRequests(bidRequests).data.pbv; + expect(pbv).to.equal('$prebid.version$'); + }); + + it('should return e parameter with value according to the adunit sizes', () => { + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600'); + }); + + it('should return correct e parameter with more than one adunit', () => { + const NEW_CODE = ADUNIT_CODE + '2'; + const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': NEW_CODE, + 'sizes': [[100, 100]], + }; + bidRequests.push(anotherBid); + + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); + }); + + it('should return correct e parameter when the adunit has no size', () => { + const noSizeBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + }; + + const e = spec.buildRequests([noSizeBid]).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':1x1'); + }); + + it('should return ur parameter with current window url', () => { + const ur = spec.buildRequests(bidRequests).data.ur; + expect(ur).to.equal(utils.getTopWindowUrl()); + }); + + it('should return fr parameter when there is a referrer', () => { + const referrer = 'thisisafakereferrer'; + const stubGetReferrer = sinon.stub(utils, 'getTopWindowReferrer').returns(referrer); + after(() => stubGetReferrer.restore()); + + const fr = spec.buildRequests(bidRequests).data.fr; + expect(fr).to.equal(referrer); + }); + + it('should return the testing url when the request has the t parameter', () => { + const url = spec.buildRequests([testBid]).url; + const expectedUrl = '//' + TEST_ISV + '/layers/t_pbjs_2.json'; + expect(url).to.equal(expectedUrl); + }); + + it('should return the parameter ncb with value 1', () => { + const ncb = spec.buildRequests(bidRequests).data.ncb; + expect(ncb).to.equal('1'); + }); + }); + + describe('interpretResponse', () => { + it('should return an empty array when there is no ads in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoAd); + expect(bidResponses).to.be.empty; + }); + + it('should return an empty array when there is no spaces in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoSpace); + expect(bidResponses).to.be.empty; + }); + + it('should correctly map the parameters in the response', () => { + const bidResponse = spec.interpretResponse(response, { adUnitToBidId: { [CLEAN_ADUNIT_CODE]: BID_ID } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ad: ADM, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); + }); + + describe('getUserSyncs', () => { + const sOptionsAllEnabled = { + pixelEnabled: true, + iframeEnabled: true + }; + const sOptionsAllDisabled = { + pixelEnabled: false, + iframeEnabled: false + }; + const sOptionsOnlyPixel = { + pixelEnabled: true, + iframeEnabled: false + }; + const sOptionsOnlyIframe = { + pixelEnabled: false, + iframeEnabled: true + }; + + it('should return an empty array if the response has no syncs', () => { + const noSyncsResponse = { cs: [] }; + const syncs = spec.getUserSyncs(sOptionsAllEnabled, [noSyncsResponse]); + expect(syncs).to.be.empty; + }); + + it('should return an empty array if there is no sync options enabled', () => { + const syncs = spec.getUserSyncs(sOptionsAllDisabled, [response]); + expect(syncs).to.be.empty; + }); + + it('should only return pixels if iframe is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyPixel, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('image')); + }); + + it('should only return iframes if pixel is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyIframe, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('iframe')); + }); + }); + + describe('adUnits mapping to bidId', () => { + it('should correctly map the bidId to the adunit', () => { + const requests = spec.buildRequests([validBid, validBid2]); + const responses = spec.interpretResponse(responseWithTwoAdunits, requests); + expect(responses[0].requestId).to.equal(BID_ID); + expect(responses[1].requestId).to.equal(BID_ID2); + }); + }); +}); diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js new file mode 100644 index 00000000000..17fbdc33591 --- /dev/null +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -0,0 +1,156 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gjirafaBidAdapter'; + +describe('gjirafaAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with placementId, minCPM and minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: 'test-div', + minCPM: 0.0001, + minCPC: 0.001 + } + })).to.equal(true); + }); + + it('bidRequest with only placementId param', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: 'test-div' + } + })).to.equal(true); + }); + + it('bidRequest with minCPM and minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + minCPM: 0.0001, + minCPC: 0.001 + } + })).to.equal(true); + }); + + it('bidRequest with no placementId, minCPM or minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'gjirafa', + 'params': { + 'placementId': '71-3' + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90], [980, 200], [980, 150], [970, 90], [970, 250]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }, + { + 'bidder': 'gjirafa', + 'params': { + 'minCPM': 0.0001, + 'minCPC': 0.001, + 'explicit': true + }, + 'adUnitCode': 'hb-inarticle', + 'transactionId': '8757194d-ea7e-4c06-abc0-cfe92bfc5295', + 'sizes': [[300, 250]], + 'bidId': '81a6dcb65e2bd9', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://gjc.gjirafa.com/Home/GetBid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.data).to.exists; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.sizes).to.equal('728x90;980x200;980x150;970x90;970x250'); + expect(requests[1].data.sizes).to.equal('300x250'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://gjc.gjirafa.com/Home/GetBid', + 'data': { + 'gjid': 2323007, + 'sizes': '728x90;980x200;980x150;970x90;970x250', + 'configId': '71-3', + 'minCPM': 0, + 'minCPC': 0, + 'allowExplicit': 0, + 'referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'https://example.com/', + 'Ad': 'test ad', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360 + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js new file mode 100644 index 00000000000..30627d4d12d --- /dev/null +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -0,0 +1,138 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/gumgumBidAdapter'; + +const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; + +describe('gumgumAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gumgum', + 'params': { + 'inScreen': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'gumgum', + 'params': { + 'inSlot': '9' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + } + ]; + + it('sends bid request to ENDPOINT via GET', () => { + const requests = spec.buildRequests(bidRequests); + const request = requests[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + expect(request.id).to.equal('30b31c1838de1e'); + }); + }) + + describe('interpretResponse', () => { + let serverResponse = { + 'ad': { + 'id': 29593, + 'width': 300, + 'height': 250, + 'ipd': 2000, + 'markup': '

I am an ad

', + 'ii': true, + 'du': null, + 'price': 0, + 'zi': 0, + 'impurl': 'http://g2.gumgum.com/ad/view', + 'clsurl': 'http://g2.gumgum.com/ad/close' + }, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let bidRequest = { + id: 12345, + sizes: [[300, 250]], + url: ENDPOINT, + method: 'GET', + pi: 3 + } + + it('should get correct bid response', () => { + let expectedResponse = { + 'ad': '

I am an ad

', + 'cpm': 0, + 'creativeId': 29593, + 'currency': 'USD', + 'height': '250', + 'netRevenue': true, + 'requestId': 12345, + 'width': '300', + // dealId: DEAL_ID, + // referrer: REFERER, + ttl: 60 + }; + expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); + }); + + it('handles nobid responses', () => { + let response = { + 'ad': {}, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let result = spec.interpretResponse({ body: response }, bidRequest); + expect(result.length).to.equal(0); + }); + }) +}); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js new file mode 100644 index 00000000000..e2cd4df9a07 --- /dev/null +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -0,0 +1,178 @@ +import {expect} from 'chai'; +import {spec} from 'modules/mantisBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('MantisAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('domain override', () => { + window.mantis_domain = 'http://foo'; + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('http://foo'); + + delete window.mantis_domain; + }); + + it('standard request', () => { + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('property=10433394'); + expect(request.url).to.include('bids[0][bidId]=30b31c1838de1e'); + expect(request.url).to.include('bids[0][config][zone]=zone'); + expect(request.url).to.include('bids[0][sizes][0][width]=300'); + expect(request.url).to.include('bids[0][sizes][0][height]=250'); + expect(request.url).to.include('bids[0][sizes][1][width]=300'); + expect(request.url).to.include('bids[0][sizes][1][height]=600'); + }); + + it('use window uuid', () => { + window.mantis_uuid = 'foo'; + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('uuid=foo'); + + delete window.mantis_uuid; + }); + + it('use storage uuid', () => { + window.localStorage.setItem('mantis:uuid', 'bar'); + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('uuid=bar'); + + window.localStorage.removeItem('mantis:uuid'); + }); + + it('detect amp', () => { + var oldContext = window.context; + + window.context = {}; + window.context.tagName = 'AMP-AD'; + window.context.canonicalUrl = 'foo'; + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('amp=true'); + expect(request.url).to.include('url=foo'); + + delete window.context.tagName; + delete window.context.canonicalUrl; + + window.context = oldContext; + }); + }); + + describe('getUserSyncs', () => { + it('iframe', () => { + let result = spec.getUserSyncs({ + iframeEnabled: true + }); + + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/iframe'); + }); + + it('pixel', () => { + let result = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/pixel'); + }); + }); + + describe('interpretResponse', () => { + it('display ads returned', () => { + let response = { + body: { + ads: [ + { + bid: 'bid', + cpm: 1, + view: 'view', + width: 300, + height: 250, + html: '' + } + ] + } + }; + + let expectedResponse = [ + { + requestId: 'bid', + cpm: 1, + width: 300, + height: 250, + ttl: 86400, + ad: '', + creativeId: 'view', + netRevenue: true, + currency: 'USD' + } + ]; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('no ads returned', () => { + let response = { + body: { + ads: [] + } + }; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js new file mode 100644 index 00000000000..db543eda433 --- /dev/null +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -0,0 +1,329 @@ +import {expect} from 'chai'; +import {spec} from 'modules/medianetBidAdapter'; + +let VALID_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [[300, 250]], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }], + VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'bidfloor': 'abcdef', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [[300, 250]], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }], + VALID_PAYLOAD = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id' + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0' + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }] + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123' + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }] + }] + }, + VALID_PARAMS = { + bidder: 'medianet', + params: { + cid: '8CUV090' + } + }, + PARAMS_WITHOUT_CID = { + bidder: 'medianet', + params: {} + }, + PARAMS_WITH_INTEGER_CID = { + bidder: 'medianet', + params: { + cid: 8867587 + } + }, + PARAMS_WITH_EMPTY_CID = { + bidder: 'medianet', + params: { + cid: '' + } + }, + SYNC_OPTIONS_BOTH_ENABLED = { + iframeEnabled: true, + pixelEnabled: true, + }, + SYNC_OPTIONS_PIXEL_ENABLED = { + iframeEnabled: false, + pixelEnabled: true, + }, + SYNC_OPTIONS_IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }, + SERVER_CSYNC_RESPONSE = [{ + body: { + ext: { + csUrl: [{ + type: 'iframe', + url: 'iframe-url' + }, { + type: 'image', + url: 'pixel-url' + }] + } + } + }], + ENABLED_SYNC_IFRAME = [{ + type: 'iframe', + url: 'iframe-url' + }], + ENABLED_SYNC_PIXEL = [{ + type: 'image', + url: 'pixel-url' + }], + SERVER_RESPONSE_CPM_MISSING = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_CPM_ZERO = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.0 + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_NOBID = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': true, + 'requestId': '3a62cf7a853f84', + 'width': 0, + 'height': 0, + 'ttl': 0, + 'netRevenue': false + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + BID_REQUEST_SIZE_AS_1DARRAY = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [300, 250], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [300, 251], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }]; + +describe('Media.net bid adapter', () => { + describe('isBidRequestValid', () => { + it('should accept valid bid params', () => { + let isValid = spec.isBidRequestValid(VALID_PARAMS); + expect(isValid).to.equal(true); + }); + + it('should reject bid if cid is not present', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITHOUT_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is not a string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_INTEGER_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is a empty string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_EMPTY_CID); + expect(isValid).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should build valid payload on bid', () => { + let requestObj = spec.buildRequests(VALID_BID_REQUEST); + expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should accept size as a one dimensional array', () => { + let bidReq = spec.buildRequests(BID_REQUEST_SIZE_AS_1DARRAY); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should ignore bidfloor if not a valid number', () => { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD); + }); + }); + + describe('getUserSyncs', () => { + it('should exclude iframe syncs if iframe is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_PIXEL_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_PIXEL); + }); + + it('should exclude pixel syncs if pixel is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_IFRAME_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + + it('should choose iframe sync urls if both sync options are enabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_BOTH_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + }); + + describe('interpretResponse', () => { + it('should not push bid response if cpm missing', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_MISSING, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push bid response if cpm 0', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_ZERO, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push response if no-bid', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBID, []); + expect(bids).to.deep.equal(validBids) + }); + }); +}); diff --git a/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js new file mode 100644 index 00000000000..7ed65718657 --- /dev/null +++ b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js @@ -0,0 +1,138 @@ +import {expect} from 'chai'; +import {spec} from 'modules/nasmediaAdmixerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('nasmediaAdmixerBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + const bid = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ax_key' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'ax_key': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ajj7jba3' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + } + ]; + + it('sends bid request to url via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.match(new RegExp(`https://adn.admixer.co.kr`)); + }); + }); + + describe('interpretResponse', () => { + const response = { + 'body': { + 'bidder': 'nasmedia_admixer', + 'req_id': '861a8e7952c82c', + 'error_code': 0, + 'error_msg': 'OK', + 'body': [{ + 'ad_id': '20049', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'cpm': 1.769221, + 'ad': '' + }] + }, + 'headers': { + 'get': function () { + } + } + }; + + const bidRequest = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ajj7jba3', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [320, 480]], + 'bidId': '31300c8b9697cd', + 'bidderRequestId': '2bf570adcf83fa', + 'auctionId': '169827a33f03cc', + }; + + it('should get correct bid response', () => { + const expectedResponse = [ + { + 'requestId': '861a8e7952c82c', + 'cpm': 1.769221, + 'currency': 'USD', + 'width': 300, + 'height': 250, + 'ad': '', + 'creativeId': '20049', + 'ttl': 360, + 'netRevenue': false + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(1); + let resultKeys = Object.keys(result[0]); + expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); + resultKeys.forEach(function (k) { + if (k === 'ad') { + expect(result[0][k]).to.match(/$/); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + + it('handles nobid responses', () => { + response.body = { + 'bidder': 'nasmedia_admixer', + 'req_id': '861a8e7952c82c', + 'error_code': 0, + 'error_msg': 'OK', + 'body': [] + }; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index eabf1f81f32..0b0e8e54ebb 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -15,7 +15,7 @@ describe('OpenxAdapter', () => { }); describe('isBidRequestValid', () => { - let bid = { + const bid = { 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -29,7 +29,7 @@ describe('OpenxAdapter', () => { 'auctionId': '1d1a030790a475', }; - let videoBid = { + const videoBid = { 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -73,7 +73,7 @@ describe('OpenxAdapter', () => { }); describe('buildRequests for banner ads', () => { - let bidRequestsWithNoMediaType = [{ + const bidRequestsWithNoMediaType = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -85,7 +85,7 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; - let bidRequests = [{ + const bidRequestsWithMediaType = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -98,21 +98,40 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': {banner: {}}, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }]; it('should send bid request to openx url via GET, even without mediaType specified', () => { const request = spec.buildRequests(bidRequestsWithNoMediaType); - expect(request[0].url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx url via GET, with mediaType specified as banner', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); expect(request[0].method).to.equal('GET'); }); - it('should send bid request to openx url via GET', () => { - const request = spec.buildRequests(bidRequests); - expect(request[0].url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + it('should send bid request to openx url via GET, with mediaTypes specified with banner type', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); expect(request[0].method).to.equal('GET'); }); it('should have the correct parameters', () => { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequestsWithNoMediaType); const dataParams = request[0].data; expect(dataParams.auid).to.exist; @@ -122,8 +141,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom params on bids that have customParams specified', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -141,8 +160,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom floors on bids that have customFloors specified', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -160,8 +179,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom bc parameter, if override is present', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -180,7 +199,25 @@ describe('OpenxAdapter', () => { }); describe('buildRequests for video', () => { - let bidRequests = [{ + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'mediaTypes': {video: {}}, + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'video': { + 'url': 'abc.com', + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + + const bidRequestsWithMediaType = [{ 'bidder': 'openx', 'mediaType': 'video', 'params': { @@ -198,14 +235,20 @@ describe('OpenxAdapter', () => { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' }]; - it('should send bid request to openx url via GET', () => { - const request = spec.buildRequests(bidRequests); - expect(request[0].url).to.equal('http://' + bidRequests[0].params.delDomain + URLBASEVIDEO); + it('should send bid request to openx url via GET, with mediaType as video', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('http://' + bidRequestsWithMediaType[0].params.delDomain + URLBASEVIDEO); + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx url via GET, with mediaTypes having video parameter', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('http://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); expect(request[0].method).to.equal('GET'); }); it('should have the correct parameters', () => { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequestsWithMediaTypes); const dataParams = request[0].data; expect(dataParams.auid).to.exist; @@ -216,7 +259,7 @@ describe('OpenxAdapter', () => { }); describe('interpretResponse for banner ads', () => { - let bids = [{ + const bids = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -229,13 +272,13 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; - let bidRequest = { + const bidRequest = { method: 'GET', url: 'url', data: {}, payload: {'bids': bids, 'startTime': new Date()} }; - let bidResponse = { + const bidResponse = { 'ads': { 'version': 1, @@ -272,7 +315,7 @@ describe('OpenxAdapter', () => { } }; it('should return correct bid response', () => { - let expectedResponse = [ + const expectedResponse = [ { 'requestId': '30b31c1838de1e', 'cpm': 1, @@ -287,12 +330,12 @@ describe('OpenxAdapter', () => { } ]; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(Object.keys(result[0])).to.eql(Object.keys(expectedResponse[0])); }); it('handles nobid responses', () => { - let bidResponse = { + const bidResponse = { 'ads': { 'version': 1, @@ -302,13 +345,13 @@ describe('OpenxAdapter', () => { } }; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(result.length).to.equal(0); }); }); describe('interpretResponse for video ads', () => { - let bids = [{ + const bids = [{ 'bidder': 'openx', 'mediaType': 'video', 'params': { @@ -325,13 +368,13 @@ describe('OpenxAdapter', () => { 'auctionId': '1d1a030790a475', 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' }]; - let bidRequest = { + const bidRequest = { method: 'GET', url: 'url', data: {}, payload: {'bid': bids[0], 'startTime': new Date()} }; - let bidResponse = { + const bidResponse = { 'pub_rev': '1', 'width': '640', 'height': '480', @@ -341,7 +384,7 @@ describe('OpenxAdapter', () => { }; it('should return correct bid response', () => { - let expectedResponse = [ + const expectedResponse = [ { 'requestId': '30b31c1838de1e', 'bidderCode': 'openx', @@ -357,13 +400,13 @@ describe('OpenxAdapter', () => { } ]; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); }); it('handles nobid responses', () => { - let bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(result.length).to.equal(0); }); }); diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index cef8975ad2b..ad113b9021a 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -2,6 +2,16 @@ import {spec} from '../../../modules/rhythmoneBidAdapter'; var assert = require('assert'); describe('rhythmone adapter tests', function () { + describe('auditBeacon', function() { + var z = spec; + var beaconURL = z.getUserSyncs({pixelEnabled: true})[0]; + + it('should contain the correct path', function() { + var u = '//hbevents.1rx.io/audit?' + assert.equal(beaconURL.url.substring(0, u.length), u); + }); + }); + describe('rhythmoneResponse', function () { var z = spec; diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 918e03674a9..6149545d59f 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -82,6 +82,7 @@ describe('TrustXAdapter', function () { expect(payload).to.have.property('u').that.is.a('string'); expect(payload).to.have.property('pt', 'net'); expect(payload).to.have.property('auids', '43'); + expect(payload).to.have.property('r', '22edbae2733bf6'); }); it('auids must not be duplicated', () => { @@ -91,6 +92,7 @@ describe('TrustXAdapter', function () { expect(payload).to.have.property('u').that.is.a('string'); expect(payload).to.have.property('pt', 'net'); expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); }); it('pt parameter must be "gross" if params.priceType === "gross"', () => { @@ -101,6 +103,7 @@ describe('TrustXAdapter', function () { expect(payload).to.have.property('u').that.is.a('string'); expect(payload).to.have.property('pt', 'gross'); expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); delete bidRequests[1].params.priceType; }); @@ -112,6 +115,7 @@ describe('TrustXAdapter', function () { expect(payload).to.have.property('u').that.is.a('string'); expect(payload).to.have.property('pt', 'net'); expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); delete bidRequests[1].params.priceType; }); }); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js new file mode 100644 index 00000000000..e8b0fbcc4b2 --- /dev/null +++ b/test/spec/modules/viBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from 'modules/viBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = `//pb.vi-serve.com/prebid/bid`; + +describe('viBidAdapter', function() { + newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [320, 480] + ], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pubId not passed', () => { + bid.params.pubId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [{ + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [320, 480] + ], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('buildRequests can handle size in 1-dim array', () => { + let bidRequests = [{ + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [320, 480], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('interpretResponse', () => { + let response = { + body: [{ + 'id': '29b891ad542377', + 'price': 0.1, + 'width': 320, + 'height': 480, + 'ad': '', + 'creativeId': 'dZsPGv' + }] + }; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '29b891ad542377', + 'cpm': 0.1, + 'width': 320, + 'height': 480, + 'creativeId': 'dZsPGv', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(``), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', () => { + let response = { + body: [] + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 977575a4d19..fa57ceed5f5 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting } from 'src/native'; +import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid } from 'src/native'; const utils = require('src/utils'); const bid = { @@ -44,3 +44,113 @@ describe('native.js', () => { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); }); + +describe('validate native', () => { + let bidReq = [{ + bids: [{ + bidderCode: 'test_bidder', + bidId: 'test_bid_id', + mediaTypes: { + native: { + title: { + required: true, + }, + body: { + required: true, + }, + image: { + required: true, + sizes: [150, 50], + aspect_ratios: [150, 50] + }, + icon: { + required: true, + sizes: [50, 50] + }, + } + } + }] + }]; + + let validBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 75, + width: 75 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 2250, + width: 3000 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + title: 'This is an example Prebid Native creative' + } + }; + + let noIconDimBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 0, + width: 0 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 2250, + width: 3000 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + title: 'This is an example Prebid Native creative' + } + }; + + let noImgDimBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 75, + width: 75 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 0, + width: 0 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + title: 'This is an example Prebid Native creative' + } + }; + + beforeEach(() => {}); + + afterEach(() => {}); + + it('should reject bid if no image sizes are defined', () => { + let result = nativeBidIsValid(validBid, bidReq); + expect(result).to.be.true; + result = nativeBidIsValid(noIconDimBid, bidReq); + expect(result).to.be.false; + result = nativeBidIsValid(noImgDimBid, bidReq); + expect(result).to.be.false; + }); +}); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 1732716c6a4..bba404448a0 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import AdapterManager from 'src/adaptermanager'; +import { checkBidRequestSizes } from 'src/adaptermanager'; import { getAdUnits } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; @@ -816,4 +817,191 @@ describe('adapterManager tests', () => { }) }); }); + + describe('isValidBidRequest', () => { + describe('positive tests for validating bid request', () => { + beforeEach(() => {}); + + afterEach(() => {}); + it('should main adUnit structure and adUnits.sizes is replaced', () => { + let fullAdUnit = [{ + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [640, 480] + }, + native: { + image: { + sizes: [150, 150], + aspect_ratios: [140, 140] + }, + icon: { + sizes: [75, 75] + } + } + } + }]; + let result = checkBidRequestSizes(fullAdUnit); + expect(result[0].sizes).to.deep.equal([640, 480]); + expect(result[0].mediaTypes.video.playerSize).to.deep.equal([640, 480]); + expect(result[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); + expect(result[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); + expect(result[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]); + + let noOptnlFieldAdUnit = [{ + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + }, + native: { + image: { + required: true + }, + icon: { + required: true + } + } + } + }]; + result = checkBidRequestSizes(noOptnlFieldAdUnit); + expect(result[0].sizes).to.deep.equal([[300, 250]]); + expect(result[0].mediaTypes.video).to.exist; + + let mixedAdUnit = [{ + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [400, 350] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } + } + } + }]; + result = checkBidRequestSizes(mixedAdUnit); + expect(result[0].sizes).to.deep.equal([400, 350]); + expect(result[0].mediaTypes.video).to.exist; + }); + }); + + describe('negative tests for validating bid requests', () => { + beforeEach(() => { + sinon.stub(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should throw error message and delete an object/property', () => { + let badBanner = [{ + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + name: 'test' + } + } + }]; + let result = checkBidRequestSizes(badBanner); + expect(result[0].sizes).to.deep.equal([[300, 250], [300, 600]]); + expect(result[0].mediaTypes.banner).to.be.undefined; + sinon.assert.called(utils.logError); + + let badVideo1 = [{ + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: '600x400' + } + } + }]; + result = checkBidRequestSizes(badVideo1); + expect(result[0].sizes).to.deep.equal([[600, 600]]); + expect(result[0].mediaTypes.video.playerSize).to.be.undefined; + expect(result[0].mediaTypes.video).to.exist; + sinon.assert.called(utils.logError); + + let badVideo2 = [{ + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['300', '200'] + } + } + }]; + result = checkBidRequestSizes(badVideo2); + expect(result[0].sizes).to.deep.equal([[600, 600]]); + expect(result[0].mediaTypes.video.playerSize).to.be.undefined; + expect(result[0].mediaTypes.video).to.exist; + sinon.assert.called(utils.logError); + + let badVideo3 = [{ + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + } + }]; + result = checkBidRequestSizes(badVideo3); + expect(result[0].sizes).to.deep.equal([[600, 600]]); + expect(result[0].mediaTypes.video.playerSize).to.be.undefined; + expect(result[0].mediaTypes.video).to.exist; + sinon.assert.called(utils.logError); + + let badNativeImgSize = [{ + mediaTypes: { + native: { + image: { + sizes: '300x250' + } + } + } + }]; + result = checkBidRequestSizes(badNativeImgSize); + expect(result[0].mediaTypes.native.image.sizes).to.be.undefined; + expect(result[0].mediaTypes.native.image).to.exist; + sinon.assert.called(utils.logError); + + let badNativeImgAspRat = [{ + mediaTypes: { + native: { + image: { + aspect_ratios: '300x250' + } + } + } + }]; + result = checkBidRequestSizes(badNativeImgAspRat); + expect(result[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; + expect(result[0].mediaTypes.native.image).to.exist; + sinon.assert.called(utils.logError); + + let badNativeIcon = [{ + mediaTypes: { + native: { + icon: { + sizes: '300x250' + } + } + } + }]; + result = checkBidRequestSizes(badNativeIcon); + expect(result[0].mediaTypes.native.icon.sizes).to.be.undefined; + expect(result[0].mediaTypes.native.icon).to.exist; + sinon.assert.called(utils.logError); + }); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index a21ba77b901..28d570aa6fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,7 +3770,7 @@ gulp-concat@^2.6.0: through2 "^2.0.0" vinyl "^2.0.0" -gulp-connect@^5.0.0: +gulp-connect@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/gulp-connect/-/gulp-connect-5.0.0.tgz#f2fdf306ae911468368c2285f2d782f13eddaf4e" dependencies: