diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000000..41b1c7ba747 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 14 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - bug +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/PR_REVIEW.md b/PR_REVIEW.md index e8c4559bbf8..2a870d9e2f6 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -33,6 +33,7 @@ For modules and core platform updates, the initial reviewer should request an ad - All user-sync (aka pixel) activity must be registered via the provided functions - Adapters may not use the $$PREBID_GLOBAL$$ variable - All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. +- Adapters may not globally override or default the standard ad server targeting values: hb_adid, hb_bidder, hb_pb, hb_deal, or hb_size, hb_source, hb_format. ## Ticket Coordinator diff --git a/build/dist/prebid.js b/build/dist/prebid.js index e1754f5023d..36bd8b2770e 100644 --- a/build/dist/prebid.js +++ b/build/dist/prebid.js @@ -1,8 +1,8 @@ -/* prebid.js v1.5.0-pre -Updated : 2018-03-06 */ -!(function(e){var t=window.pbjsChunk;window.pbjsChunk=function(n,o,a){for(var u,d,s,c=0,f=[];c0)if(2===a&&"number"==typeof e[0]&&"number"==typeof e[1])t.push(b(e));else for(var u=0;u0&&void 0!==arguments[0]?arguments[0]:pbjs.adUnits).map((function(e){return e.bids.map((function(e){return e.bidder})).reduce(A,[])})).reduce(A).filter(T)},t.isGptPubadsDefined=function(){if(window.googletag&&t.isFn(window.googletag.pubads)&&t.isFn(window.googletag.pubads().getSlots))return!0},t.getHighestCpm=function(e,t){if(e.cpm===t.cpm)return e.timeToRespond>t.timeToRespond?t:e;return e.cpm0;){var n=Math.floor(Math.random()*t),r=e[--t];e[t]=e[n],e[n]=r}return e},t.adUnitsFilter=function(e,t){return(0,d.default)(e,t&&t.adUnitCode)},t.isSrcdocSupported=function(e){return e.defaultView&&e.defaultView.frameElement&&"srcdoc"in e.defaultView.frameElement&&!/firefox/i.test(navigator.userAgent)},t.deepClone=function(e){return(0,a.default)(e)},t.inIframe=function(){try{return window.self!==window.top}catch(e){return!0}},t.isSafariBrowser=function(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)},t.replaceAuctionPrice=function(e,t){if(!e)return;return e.replace(/\$\{AUCTION_PRICE\}/g,t)},t.timestamp=function(){return(new Date).getTime()},t.checkCookieSupport=function(){if(window.navigator.cookieEnabled||document.cookie.length)return!0},t.cookiesAreEnabled=function(){if(t.checkCookieSupport())return!0;return window.document.cookie="prebid.cookieTest",-1!=window.document.cookie.indexOf("prebid.cookieTest")},t.delayExecution=function(e,t){if(t<1)throw new Error("numRequiredCalls must be a positive number. Got "+t);var n=0;return function(){++n===t&&e.apply(null,arguments)}},t.groupBy=function(e,t){return e.reduce((function(e,n){return(e[n[t]]=e[n[t]]||[]).push(n),e}),{})},t.deepAccess=function(e,t){t=String(t).split(".");for(var n=0;n + + creativeHtml + + ` + .replace('creativeHtml', bidModel.CreativeHtml); +} + +function getCappedCampaignsAsString() { + const key = 'ivvcap'; + + let loadData = function () { + try { + return JSON.parse(localStorage.getItem(key)) || {}; + } catch (e) { + return {}; + } + }; + + let saveData = function (data) { + localStorage.setItem(key, JSON.stringify(data)); + }; + + let clearExpired = function () { + let now = new Date().getTime(); + let data = loadData(); + let dirty = false; + Object.keys(data).forEach(function (k) { + let exp = data[k][1]; + if (exp <= now) { + delete data[k]; + dirty = true; + } + }); + if (dirty) { + saveData(data); + } + }; + + let getCappedCampaigns = function () { + clearExpired(); + let data = loadData(); + return Object.keys(data) + .filter(function (k) { return data.hasOwnProperty(k); }) + .sort() + .map(function (k) { return [k, data[k][0]]; }); + }; + + return getCappedCampaigns() + .map(function (record) { return record.join('='); }) + .join(','); +} + +function initLogger() { + const noop = function () { }; + + if (localStorage && localStorage.InvibesDEBUG) { + return window.console; + } + + return { info: noop, error: noop, log: noop, warn: noop, debug: noop }; +} + +/// Local domain cookie management ===================== +let Uid = { + generate: function () { + let date = new Date().getTime(); + if (date > 151 * 10e9) { + let datePart = Math.floor(date / 1000).toString(36); + let maxRand = parseInt('zzzzzz', 36) + let randPart = Math.floor(Math.random() * maxRand).toString(36); + return datePart + '.' + randPart; + } + }, + getCrTime: function (s) { + let toks = s.split('.'); + return parseInt(toks[0] || 0, 36) * 1e3; + } +}; + +let cookieDomain, noCookies; +function getCookie(name) { + let i, x, y; + let cookies = document.cookie.split(';'); + for (i = 0; i < cookies.length; i++) { + x = cookies[i].substr(0, cookies[i].indexOf('=')); + y = cookies[i].substr(cookies[i].indexOf('=') + 1); + x = x.replace(/^\s+|\s+$/g, ''); + if (x === name) { + return unescape(y); + } + } +}; + +function setCookie(name, value, exdays, domain) { + if (noCookies && name != 'ivNoCookie' && (exdays || 0) >= 0) { return; } + if (exdays > 365) { exdays = 365; } + domain = domain || cookieDomain; + let exdate = new Date(); + let exms = exdays * 24 * 60 * 60 * 1000; + exdate.setTime(exdate.getTime() + exms); + let cookieValue = value + ((!exdays) ? '' : '; expires=' + exdate.toUTCString()); + cookieValue += ';domain=' + domain + ';path=/'; + document.cookie = name + '=' + cookieValue; +}; + +let detectTopmostCookieDomain = function () { + let testCookie = Uid.generate(); + let hostParts = location.host.split('.'); + if (hostParts.length === 1) { + return location.host; + } + for (let i = hostParts.length - 1; i >= 0; i--) { + let domain = '.' + hostParts.slice(i).join('.'); + setCookie(testCookie, testCookie, 1, domain); + let val = getCookie(testCookie); + if (val === testCookie) { + setCookie(testCookie, testCookie, -1, domain); + return domain; + } + } +}; +cookieDomain = detectTopmostCookieDomain(); +noCookies = getCookie('ivNoCookie'); + +function initDomainId(invibes, persistence) { + if (typeof invibes.dom === 'object') { + return; + } + + let cookiePersistence = { + cname: 'ivbsdid', + load: function () { + let str = getCookie(this.cname) || ''; + try { + return JSON.parse(str); + } catch (e) { } + }, + save: function (obj) { + setCookie(this.cname, JSON.stringify(obj), 365); + } + }; + + persistence = persistence || cookiePersistence; + let state; + const minHC = 5; + + let validGradTime = function (d) { + const min = 151 * 10e9; + let yesterday = new Date().getTime() - 864 * 10e4 + return d > min && d < yesterday; + }; + + state = persistence.load() || { + id: Uid.generate(), + hc: 1, + temp: 1 + }; + + let graduate; + + let setId = function () { + invibes.dom = { + id: state.temp ? undefined : state.id, + tempId: state.id, + graduate: graduate + }; + }; + + graduate = function () { + if (!state.temp) { return; } + delete state.temp; + delete state.hc; + persistence.save(state); + setId(); + } + + if (state.temp) { + if (state.hc < minHC) { + state.hc++; + } + if (state.hc >= minHC && validGradTime(Uid.getCrTime(state.id))) { + graduate(); + } + } + + persistence.save(state); + setId(); +}; +// ===================== diff --git a/modules/invibesBidAdapter.md b/modules/invibesBidAdapter.md new file mode 100644 index 00000000000..06afba6344d --- /dev/null +++ b/modules/invibesBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Invibes Bid Adapter +Module Type: Bidder Adapter +Maintainer: system_operations@invibes.com +``` + +# Description + +Connect to Invibes for bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[400, 300]], + bids: [ + { + bidder: 'invibes', + params: { + placementId: '12345' + } + } + ] + } + ] +``` diff --git a/modules/nasmediaAdmixerBidAdapter.js b/modules/nasmediaAdmixerBidAdapter.js index 344bf991610..7de5c638c9e 100644 --- a/modules/nasmediaAdmixerBidAdapter.js +++ b/modules/nasmediaAdmixerBidAdapter.js @@ -1,5 +1,6 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; +import find from 'core-js/library/fn/array/find'; const ADMIXER_ENDPOINT = 'https://adn.admixer.co.kr:10443/prebid'; const DEFAULT_BID_TTL = 360; @@ -62,7 +63,7 @@ function getOsType() { let os = ['android', 'ios', 'mac', 'linux', 'window']; let regexp_os = [/android/i, /iphone|ipad/i, /mac/i, /linux/i, /window/i]; - return os.find((tos, idx) => { + return find(os, (tos, idx) => { if (ua.match(regexp_os[idx])) { return os[idx]; } diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 181cf80d5e7..cd60925b2c7 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from 'src/utils'; import { BANNER } from 'src/mediaTypes'; import { registerBidder } from 'src/adapters/bidderFactory'; +import includes from 'core-js/library/fn/array/includes'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -70,7 +71,7 @@ export const spec = { supportedMediaTypes: [BANNER], isBidRequestValid: function (bid) { - return !!(REGIONS.includes(bid.params.region) && bid.params.publisherId); + return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); }, buildRequests: function (validBidRequests) { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index dd796752a54..625d34b0648 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -138,7 +138,7 @@ export const spec = { let slotData = { site_id: params.siteId, zone_id: params.zoneId, - position: params.position || 'btf', + position: parsePosition(params.position), floor: parseFloat(params.floor) > 0.01 ? params.floor : 0.01, element_id: bidRequest.adUnitCode, name: bidRequest.adUnitCode, @@ -273,7 +273,6 @@ export const spec = { requestId: bidRequest.bidId, currency: 'USD', creativeId: ad.creative_id, - mediaType: ad.creative_type, cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes @@ -284,6 +283,10 @@ export const spec = { } }; + if (ad.creative_type) { + bid.mediaType = ad.creative_type; + } + if (bidRequest.mediaType === 'video') { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; @@ -390,6 +393,13 @@ function mapSizes(sizes) { }, []); } +function parsePosition(position) { + if (position === 'atf' || position === 'btf') { + return position; + } + return 'unknown'; +} + export function masSizeOrdering(sizes) { const MAS_SIZE_PRIORITY = [15, 2, 9]; diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js new file mode 100644 index 00000000000..0c553b567ef --- /dev/null +++ b/modules/smartyadsBidAdapter.js @@ -0,0 +1,91 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; +import * as utils from 'src/utils'; + +const BIDDER_CODE = 'smartyads'; +const URL = '//ssp-nj.webtradehub.com/?c=o&m=multi'; +const URL_SYNC = '//ssp-nj.webtradehub.com/?c=o&m=cookie'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid['mediaType']) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.title && bid.image && bid.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); + }, + + buildRequests: (validBidRequests = []) => { + let winTop = window; + try { + window.top.location.toString(); + winTop = window.top; + } catch (e) { + utils.logMessage(e); + } + let location = utils.getTopWindowLocation(); + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': location.protocol === 'https:' ? 1 : 0, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + traffic: bid.params.traffic || BANNER + }); + } + return { + method: 'POST', + url: URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + serverResponse = serverResponse.body; + for (let i = 0; i < serverResponse.length; i++) { + let resItem = serverResponse[i]; + if (isBidResponseValid(resItem)) { + delete resItem.mediaType; + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + return [{ + type: 'image', + url: URL_SYNC + }]; + } + +}; + +registerBidder(spec); diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md new file mode 100644 index 00000000000..5102a6fd128 --- /dev/null +++ b/modules/smartyadsBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: SmartyAds Bidder Adapter +Module Type: Bidder Adapter +Maintainer: supply@smartyads.com +``` + +# Description + +Module that connects to SmartyAds' demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId_0', + sizes: [[300, 250]], + bids: [{ + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } + }] + } + ]; +``` \ No newline at end of file diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 981f5603a0a..411c18f5414 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; import {BANNER} from 'src/mediaTypes'; -export const URL = '//prebid.cliipa.com'; +export const URL = '//prebid.nininin.com'; const BIDDER_CODE = 'vidazoo'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; @@ -85,7 +85,7 @@ function getUserSyncs(syncOptions, responses) { if (iframeEnabled) { return [{ type: 'iframe', - url: '//static.cliipa.com/basev/sync/user_sync.html' + url: '//static.nininin.com/basev/sync/user_sync.html' }]; } diff --git a/modules/vubleBidAdapter.js b/modules/vubleBidAdapter.js new file mode 100644 index 00000000000..c995705c957 --- /dev/null +++ b/modules/vubleBidAdapter.js @@ -0,0 +1,135 @@ +// Vuble Adapter + +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'vuble'; + +const ENVS = ['com', 'net']; +const CURRENCIES = { + com: 'EUR', + net: 'USD' +}; +const TTL = 60; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['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) { + if (utils.isEmpty(bid.sizes) || utils.parseSizesInput(bid.sizes).length == 0) { + return false; + } + + if (!utils.deepAccess(bid, 'mediaTypes.video.context')) { + return false; + } + + if (!utils.contains(ENVS, bid.params.env)) { + return false; + } + + return !!(bid.params.env && bid.params.pubId && bid.params.zoneId); + }, + + /** + * 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) { + return validBidRequests.map(bid => { + // We take the first size + let size = utils.parseSizesInput(bid.sizes)[0].split('x'); + + // Get the page's url + let referrer = utils.getTopWindowUrl(); + if (bid.params.referrer) { + referrer = bid.params.referrer; + } + + // Get Video Context + let context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + let url = '//player.mediabong.' + bid.params.env + '/prebid/request'; + let data = { + width: size[0], + height: size[1], + pub_id: bid.params.pubId, + zone_id: bid.params.zoneId, + context: context, + floor_price: bid.params.floorPrice ? bid.params.floorPrice : 0, + url: referrer, + env: bid.params.env, + bid_id: bid.bidId + }; + + return { + method: 'POST', + url: url, + data: data + }; + }); + }, + + /** + * 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, bid) { + const responseBody = serverResponse.body; + + if (typeof responseBody !== 'object' || responseBody.status !== 'ok') { + return []; + } + + let responses = []; + let reponse = { + requestId: bid.data.bid_id, + cpm: responseBody.cpm, + width: bid.data.width, + height: bid.data.height, + ttl: TTL, + creativeId: responseBody.creativeId, + netRevenue: true, + currency: CURRENCIES[bid.data.env], + vastUrl: responseBody.url + }; + responses.push(reponse); + + return responses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses) { + if (syncOptions.iframeEnabled) { + if (serverResponses.length > 0) { + let responseBody = serverResponses[0].body; + if (typeof responseBody !== 'object' || responseBody.iframeSync) { + return [{ + type: 'iframe', + url: responseBody.iframeSync + }]; + } + } + } + return []; + } +}; + +registerBidder(spec); diff --git a/modules/vubleBidAdapter.md b/modules/vubleBidAdapter.md new file mode 100644 index 00000000000..4e066b9dd8a --- /dev/null +++ b/modules/vubleBidAdapter.md @@ -0,0 +1,58 @@ +# Overview + +``` +Module Name: Vuble Bidder Adapter +Module Type: Vuble Adapter +Maintainer: gv@mediabong.com +``` + +# Description + +Module that connects to Vuble's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-video-instream', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [ + { + bidder: "vuble", + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + referrer: "http://www.vuble.tv/", // optional + floorPrice: 5.00 // optional + } + } + ] + }, + { + code: 'test-video-outstream', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [ + { + bidder: "vuble", + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + referrer: "http://www.vuble.tv/", // optional + floorPrice: 5.00 // optional + } + } + ] + } + ]; \ No newline at end of file diff --git a/modules/yieldbotBidAdapter.js b/modules/yieldbotBidAdapter.js new file mode 100644 index 00000000000..bf21613e31f --- /dev/null +++ b/modules/yieldbotBidAdapter.js @@ -0,0 +1,604 @@ +import * as utils from 'src/utils'; +import { formatQS as buildQueryString } from '../src/url'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +/** + * @module {BidderSpec} YieldbotBidAdapter + * @description Adapter for requesting bids from Yieldbot + * @see BidderSpec + * @author [elljoh]{@link https://github.com/elljoh} + * @private + */ +export const YieldbotAdapter = { + _adapterLoaded: Date.now(), + _navigationStart: 0, + _version: 'pbjs-yb-0.0.1', + _bidRequestCount: 0, + _pageviewDepth: 0, + _lastPageviewId: '', + _sessionBlocked: false, + _isInitialized: false, + _sessionTimeout: 180000, + _userTimeout: 2592000000, + _cookieLabels: ['n', 'u', 'si', 'pvd', 'lpv', 'lpvi', 'c'], + + initialize: function() { + if (!this._isInitialized) { + this._pageviewDepth = this.pageviewDepth; + this._sessionBlocked = this.sessionBlocked; + this._isInitialized = true; + } + }, + + /** + * Adapter version + * pbjs-yb-x.x.x + * @returns {string} The adapter version string + * @memberof module:YieldbotBidAdapter + */ + get version() { + return this._version; + }, + + /** + * Is the user session blocked by the Yieldbot adserver.
+ * The Yieldbot adserver may return "block_session": true in a bid response. + * A session may be blocked for efficiency (i.e. Yieldbot has decided no to bid for the session), + * security and/or fraud detection. + * @returns {boolean} + * @readonly + * @memberof module:YieldbotBidAdapter + * @private + */ + get sessionBlocked() { + const cookieName = '__ybotn'; + const cookieValue = this.getCookie(cookieName); + const sessionBlocked = cookieValue ? parseInt(cookieValue, 10) || 0 : 0; + if (sessionBlocked) { + this.setCookie(cookieName, 1, this._sessionTimeout, '/'); + } + this._sessionBlocked = !!sessionBlocked; + return this._sessionBlocked; + }, + + set sessionBlocked(blockSession) { + const cookieName = '__ybotn'; + const sessionBlocked = blockSession ? 1 : 0; + this.setCookie(cookieName, sessionBlocked, this._sessionTimeout, '/'); + }, + + get userId() { + const cookieName = '__ybotu'; + let cookieValue = this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = this.newId(); + this.setCookie(cookieName, cookieValue, this._userTimeout, '/'); + } + return cookieValue; + }, + + get sessionId() { + const cookieName = '__ybotsi'; + let cookieValue = this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = this.newId(); + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + } + return cookieValue; + }, + + get pageviewDepth() { + const cookieName = '__ybotpvd'; + let cookieValue = parseInt(this.getCookie(cookieName), 10) || 0; + cookieValue++; + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + return cookieValue; + }, + + get lastPageviewId() { + const cookieName = '__ybotlpvi'; + let cookieValue = this.getCookie(cookieName); + return cookieValue || ''; + }, + + set lastPageviewId(id) { + const cookieName = '__ybotlpvi'; + this.setCookie(cookieName, id, this._sessionTimeout, '/'); + }, + + get lastPageviewTime() { + const cookieName = '__ybotlpv'; + let cookieValue = this.getCookie(cookieName); + return cookieValue ? parseInt(cookieValue, 10) : 0; + }, + + set lastPageviewTime(ts) { + const cookieName = '__ybotlpv'; + this.setCookie(cookieName, ts, this._sessionTimeout, '/'); + }, + + /** + * Get/set the request base url used to form bid, ad markup and impression requests. + * @param {string} [prefix] the bidder request base url + * @returns {string} the request base Url string + * @memberof module:YieldbotBidAdapter + */ + urlPrefix: function(prefix) { + const cookieName = '__ybotc'; + const pIdx = prefix ? prefix.indexOf(':') : -1; + const url = pIdx !== -1 ? document.location.protocol + prefix.substr(pIdx + 1) : null; + let cookieValue = url || this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = '//i.yldbt.com/m/'; + } + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + return cookieValue; + }, + + /** + * Bidder identifier code. + * @type {string} + * @constant + * @memberof module:YieldbotBidAdapter + */ + get code() { return 'yieldbot'; }, + + /** + * A list of aliases which should also resolve to this bidder. + * @type {string[]} + * @constant + * @memberof module:YieldbotBidAdapter + */ + get aliases() { return []; }, + + /** + * @property {MediaType[]} [supportedMediaTypes]: A list of Media Types which the adapter supports. + * @constant + * @memberof module:YieldbotBidAdapter + */ + get supportedMediaTypes() { return ['banner']; }, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise. + * @memberof module:YieldbotBidAdapter + */ + isBidRequestValid: function(bid) { + let invalidSizeArray = false; + if (utils.isArray(bid.sizes)) { + const arr = bid.sizes.reduce((acc, cur) => acc.concat(cur), []).filter((item) => { + return !utils.isNumber(item); + }); + invalidSizeArray = arr.length !== 0; + } + const ret = bid && + bid.params && + utils.isStr(bid.params.psn) && + utils.isStr(bid.params.slot) && + !invalidSizeArray && + !!bid.params.psn && + !!bid.params.slot; + return ret; + }, + + /** + * 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. + * @memberof module:YieldbotBidAdapter + */ + buildRequests: function(bidRequests) { + const requests = []; + if (!this._optOut && !this.sessionBlocked) { + const searchParams = this.initBidRequestParams(); + this._bidRequestCount++; + + const pageviewIdToMap = searchParams['pvi']; + + const yieldbotSlotParams = this.getSlotRequestParams(pageviewIdToMap, bidRequests); + + searchParams['sn'] = yieldbotSlotParams['sn'] || ''; + searchParams['ssz'] = yieldbotSlotParams['ssz'] || ''; + + const bidUrl = this.urlPrefix() + yieldbotSlotParams.psn + '/v1/init'; + + searchParams['cts_ini'] = Date.now(); + requests.push({ + method: 'GET', + url: bidUrl, + data: searchParams, + yieldbotSlotParams: yieldbotSlotParams, + options: { + withCredentials: true, + customHeaders: { + Accept: 'application/json' + } + } + }); + } + return requests; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + * @memberof module:YieldbotBidAdapter + */ + getUserSyncs: function(syncOptions, serverResponses) { + const userSyncs = []; + if (syncOptions.pixelEnabled && + serverResponses.length > 0 && + serverResponses[0].body && + serverResponses[0].body.user_syncs && + utils.isArray(serverResponses[0].body.user_syncs)) { + const responseUserSyncs = serverResponses[0].body.user_syncs; + responseUserSyncs.forEach((pixel) => { + userSyncs.push({ + type: 'image', + url: pixel + }); + }); + } + return userSyncs; + }, + + /** + * @typeDef {YieldbotBid} YieldbotBid + * @type {object} + * @property {string} slot Yieldbot config param slot + * @property {string} cpm Yieldbot bid quantity/label + * @property {string} size Slot dimensions of the form <width>x<height> + * @memberof module:YieldbotBidAdapter + */ + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest Request object submitted which produced the response. + * @return {Bid[]} An array of bids which were nested inside the server. + * @memberof module:YieldbotBidAdapter + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const responseBody = serverResponse && serverResponse.body ? serverResponse.body : {}; + this._optOut = responseBody.optout || false; + if (this._optOut) { + this.clearAllCookies(); + } + if (!this._optOut && !this._sessionBlocked) { + const slotBids = responseBody.slots && responseBody.slots.length > 0 ? responseBody.slots : []; + slotBids.forEach((bid) => { + if (bid.slot && bid.size && bid.cpm) { + const sizeParts = bid.size ? bid.size.split('x') : [1, 1]; + const width = sizeParts[0] || 1; + const height = sizeParts[1] || 1; + const cpm = parseInt(bid.cpm, 10) / 100.0 || 0; + + const yieldbotSlotParams = bidRequest.yieldbotSlotParams || null; + const ybBidId = bidRequest.data['pvi']; + const paramKey = `${ybBidId}:${bid.slot}:${bid.size || ''}`; + const bidIdMap = yieldbotSlotParams && yieldbotSlotParams.bidIdMap ? yieldbotSlotParams.bidIdMap : {}; + const requestId = bidIdMap[paramKey] || ''; + + const urlPrefix = this.urlPrefix(responseBody.url_prefix); + const tagObject = this.buildAdCreativeTag(urlPrefix, bid, bidRequest); + const bidResponse = { + requestId: requestId, + cpm: cpm, + width: width, + height: height, + creativeId: tagObject.creativeId, + currency: 'USD', + netRevenue: true, + ttl: this._sessionTimeout / 1000, // [s] + ad: tagObject.ad + }; + bidResponses.push(bidResponse); + } + }); + } + return bidResponses; + }, + + /** + * Initializes search parameters common to both ad request and impression Urls. + * @param {string} adRequestId Yieldbot ad request identifier + * @param {BidRequest} bidRequest The request that is the source of the impression + * @returns {object} Search parameter key/value pairs + * @memberof module:YieldbotBidAdapter + */ + initAdRequestParams: function(adRequestId, bidRequest) { + let commonSearchParams = {}; + commonSearchParams['v'] = this._version; + commonSearchParams['vi'] = bidRequest.data.vi || this._version + '-vi'; + commonSearchParams['si'] = bidRequest.data.si || this._version + '-si'; + commonSearchParams['pvi'] = bidRequest.data.pvi || this._version + '-pvi'; + commonSearchParams['ri'] = adRequestId; + return commonSearchParams; + }, + + buildAdUrl: function(urlPrefix, publisherNumber, commonSearchParams, bid) { + const searchParams = Object.assign({}, commonSearchParams); + searchParams['cts_res'] = Date.now(); + searchParams['slot'] = bid.slot + ':' + bid.size; + searchParams['ioa'] = this.intersectionObserverAvailable(window); + + const queryString = buildQueryString(searchParams) || ''; + const adUrl = urlPrefix + + publisherNumber + + '/ad/creative.js?' + + queryString; + return adUrl; + }, + + buildImpressionUrl: function(urlPrefix, publisherNumber, commonSearchParams) { + const searchParams = Object.assign({}, commonSearchParams); + const queryString = buildQueryString(searchParams) || ''; + const impressionUrl = urlPrefix + + publisherNumber + + '/ad/impression.gif?' + + queryString; + return impressionUrl; + }, + + /** + * Object with Yieldbot ad markup representation and unique creative identifier. + * @typeDef {TagObject} TagObject + * @type {object} + * @property {string} creativeId bidder specific creative identifier for tracking at the source + * @property {string} ad ad creative markup + * @memberof module:YieldbotBidAdapter + */ + /** + * Builds the ad creative markup. + * @param {string} urlPrefix base url for Yieldbot requests + * @param {module:YieldbotBidAdapter.YieldbotBid} bid Bidder slot bid object + * @returns {module:YieldbotBidAdapter.TagObject} + * @memberof module:YieldbotBidAdapter + */ + buildAdCreativeTag: function(urlPrefix, bid, bidRequest) { + const ybotAdRequestId = this.newId(); + const commonSearchParams = this.initAdRequestParams(ybotAdRequestId, bidRequest); + const publisherNumber = bidRequest && bidRequest.yieldbotSlotParams ? bidRequest.yieldbotSlotParams.psn || '' : ''; + const adUrl = this.buildAdUrl(urlPrefix, publisherNumber, commonSearchParams, bid); + const impressionUrl = this.buildImpressionUrl(urlPrefix, publisherNumber, commonSearchParams); + + const htmlMarkup = `
`; + return { ad: htmlMarkup, creativeId: ybotAdRequestId }; + }, + + intersectionObserverAvailable: function (win) { + /* Ref: + * https://github.com/w3c/IntersectionObserver/blob/gh-pages/polyfill/intersection-observer.js#L23-L25 + */ + return win && + win.IntersectionObserver && + win.IntersectionObserverEntry && + win.IntersectionObserverEntry.prototype && + 'intersectionRatio' in win.IntersectionObserverEntry.prototype; + }, + + /** + * @typeDef {BidParams} BidParams + * @property {string} psn Yieldbot publisher customer number + * @property {object} searchParams bid request Url search parameters + * @property {object} searchParams.sn slot names + * @property {object} searchParams.szz slot sizes + * @memberof module:YieldbotBidAdapter + * @private + */ + /** + * Builds the common Yieldbot bid request Url query parameters.
+ * Slot bid related parameters are handled separately. The so-called common parameters + * here are request identifiers, page properties and user-agent attributes. + * @returns {object} query parameter key/value items + * @memberof module:YieldbotBidAdapter + */ + initBidRequestParams: function() { + const params = {}; + + params['cts_js'] = this._adapterLoaded; + params['cts_ns'] = this._navigationStart; + params['v'] = this._version; + + const userId = this.userId; + const sessionId = this.sessionId; + const pageviewId = this.newId(); + const currentBidTime = Date.now(); + const lastBidTime = this.lastPageviewTime; + const lastBidId = this.lastPageviewId; + this.lastPageviewTime = currentBidTime; + this.lastPageviewId = pageviewId; + params['vi'] = userId; + params['si'] = sessionId; + params['pvd'] = this._pageviewDepth; + params['pvi'] = pageviewId; + params['lpv'] = lastBidTime; + params['lpvi'] = lastBidId; + params['bt'] = this._bidRequestCount === 0 ? 'init' : 'refresh'; + + params['ua'] = navigator.userAgent; + params['np'] = navigator.platform; + params['la'] = + navigator.browserLanguage ? navigator.browserLanguage : navigator.language; + params['to'] = + (new Date()).getTimezoneOffset() / 60; + params['sd'] = + window.screen.width + 'x' + window.screen.height; + + params['lo'] = utils.getTopWindowUrl(); + params['r'] = utils.getTopWindowReferrer(); + + params['e'] = ''; + + return params; + }, + + /** + * Bid mapping key to the Prebid internal bidRequestId
+ * Format {pageview id}:{slot name}:{width}x{height} + * @typeDef {BidRequestKey} BidRequestKey + * @type {string} + * @example "jbgxsxqxyxvqm2oud7:leaderboard:728x90" + * @memberof module:YieldbotBidAdapter + */ + + /** + * Internal Yieldbot adapter bid and ad markup request state + *
+ * When interpreting a server response, the associated requestId is lookeded up + * in this map when creating a {@link Bid} response object. + * @typeDef {BidRequestMapping} BidRequestMapping + * @type {object} + * @property {Object.} {*} Yieldbot bid to requestId mapping item + * @memberof module:YieldbotBidAdapter + * @example + * { + * "jbgxsxqxyxvqm2oud7:leaderboard:728x90": "2b7e31676ce17", + * "jbgxsxqxyxvqm2oud7:medrec:300x250": "2b7e31676cd89", + * "jcrvvd6eoileb8w8ko:medrec:300x250": "2b7e316788ca7" + * } + * @memberof module:YieldbotBidAdapter + */ + + /** + * Rationalized set of Yieldbot bids for adUnits and mapping to respective Prebid.js bidId. + * @typeDef {BidSlots} BidSlots + * @property {string} psn Yieldbot publisher site identifier taken from bidder params + * @property {string} sn slot names + * @property {string} szz slot sizes + * @property {module:YieldbotBidAdapter.BidRequestMapping} bidIdMap Yieldbot bid to Prebid bidId mapping + * @memberof module:YieldbotBidAdapter + */ + + /** + * Gets unique slot name and sizes for query parameters object + * @param {string} pageviewId The Yieldbot bid request identifier + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server + * @returns {module:YieldbotBidAdapter.BidSlots} Yieldbot specific bid parameters and bid identifier mapping + * @memberof module:YieldbotBidAdapter + */ + getSlotRequestParams: function(pageviewId, bidRequests) { + const params = {}; + const bidIdMap = {}; + bidRequests = bidRequests || []; + pageviewId = pageviewId || ''; + try { + const slotBids = {}; + bidRequests.forEach((bid) => { + params.psn = params.psn || bid.params.psn || ''; + utils.parseSizesInput(bid.sizes).forEach(sz => { + const slotName = bid.params.slot; + if (sz && (!slotBids[slotName] || !slotBids[slotName].some(existingSize => existingSize === sz))) { + slotBids[slotName] = slotBids[slotName] || []; + slotBids[slotName].push(sz); + const paramKey = pageviewId + ':' + slotName + ':' + sz; + bidIdMap[paramKey] = bid.bidId; + } + }); + }); + + const nm = []; + const sz = []; + for (let idx in slotBids) { + const slotName = idx; + const slotSizes = slotBids[idx]; + nm.push(slotName); + sz.push(slotSizes.join('.')); + } + params['sn'] = nm.join('|'); + params['ssz'] = sz.join('|'); + + params.bidIdMap = bidIdMap; + } catch (err) {} + return params; + }, + + getCookie: function(name) { + const cookies = document.cookie.split(';'); + let value = null; + for (let idx = 0; idx < cookies.length; idx++) { + const item = cookies[idx].split('='); + const itemName = item[0].replace(/^\s+|\s+$/g, ''); + if (itemName == name) { + value = item.length > 1 ? decodeURIComponent(item[1].replace(/^\s+|\s+$/g, '')) : null; + break; + } + } + return value; + }, + + setCookie: function(name, value, expireMillis, path, domain, secure) { + const dataValue = encodeURIComponent(value); + const cookieStr = name + '=' + dataValue + + (expireMillis ? ';expires=' + new Date(Date.now() + expireMillis).toGMTString() : '') + + (path ? ';path=' + path : '') + + (domain ? ';domain=' + domain : '') + + (secure ? ';secure' : ''); + + document.cookie = cookieStr; + }, + + deleteCookie: function(name, path, domain, secure) { + return this.setCookie(name, '', -1, path, domain, secure); + }, + + /** + * Clear all first-party cookies. + */ + clearAllCookies: function() { + const labels = this._cookieLabels; + for (let idx = 0; idx < labels.length; idx++) { + const label = '__ybot' + labels[idx]; + this.deleteCookie(label); + } + }, + + /** + * Generate a new Yieldbot format id
+ * Base 36 and lowercase: <[ms] since epoch><[base36]{10}> + * @example "jbgxsyrlx9fxnr1hbl" + * @private + */ + newId: function() { + return (+new Date()).toString(36) + 'xxxxxxxxxx' + .replace(/[x]/g, function() { + return (0 | Math.random() * 36).toString(36); + }); + }, + + /** + * Create a delegate function with 'this' context of the YieldbotAdapter object. + * @param {object} instance Object for 'this' context in function apply + * @param {function} fn Function to execute in instance context + * @returns {function} the provided function bound to the instance context + * @memberof module:YieldbotBidAdapter + */ + createDelegate: function(instance, fn) { + var outerArgs = Array.prototype.slice.call(arguments, 2); + return function() { + return fn.apply(instance, outerArgs.length > 0 ? Array.prototype.slice.call(arguments, 0).concat(outerArgs) : arguments); + }; + } +}; + +YieldbotAdapter.initialize(); + +export const spec = { + code: YieldbotAdapter.code, + aliases: YieldbotAdapter.aliases, + supportedMediaTypes: YieldbotAdapter.supportedMediaTypes, + isBidRequestValid: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.isBidRequestValid), + buildRequests: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.buildRequests), + interpretResponse: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.interpretResponse), + getUserSyncs: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.getUserSyncs) +}; + +YieldbotAdapter._navigationStart = Date.now(); +registerBidder(spec); diff --git a/modules/yieldbotBidAdapter.md b/modules/yieldbotBidAdapter.md new file mode 100644 index 00000000000..db6f4dc100b --- /dev/null +++ b/modules/yieldbotBidAdapter.md @@ -0,0 +1,192 @@ +# Overview + +``` +Module Name: Yieldbot Bid Adapter +Module Type: Bidder Adapter +Maintainer: pubops@yieldbot.com +``` + +# Description +The Yieldbot Prebid.js bid adapter integrates Yieldbot demand to publisher inventory. + +# BaseAdapter Settings + +| Setting | Value | +| :-------------------- | :------------ | +| `supportedMediaTypes` | **banner** | +| `getUserSyncs` | **image** pixel | +| `ttl` | **180** [s] | +| `currency` | **USD** | + +# Parameters +The following table lists parameters required for Yieldbot bidder configuration. +See also [Test Parameters](#test-parameters) for an illustration of parameter usage. + +| Name | Scope | Description | Example | +| :------ | :------- | :------------------------------------------------------------------ | :-------------- | +| `psn` | required | The Yieldbot publisher account short name identifier | "7b25" | +| `slot` | required | The Yieldbot slot name associated to the publisher adUnit to bid on | "mobile_REC_2" | + +## Example Bidder Configuration +```javascript +var adUnit0 = { + code: '/00000000/leaderboard', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '7b25', + slot: 'desktop_LB' + } + } + ] +}; +``` + +# Test Parameters +For integration testing, the Yieldbot Platform can be set to always return a bid for requested slots. + +When Yieldbot testing mode is enabled, a cookie (`__ybot_test`) on the domain `.yldbt.com` tells the Yieldbot ad server to always return a bid. Each bid is associated to a static mock integration testing creative. + +- **Enable** integration testing mode: + - http://i.yldbt.com/integration/start +- **Disable** integration testing mode: + - http://i.yldbt.com/integration/stop + +***Note:*** + +- No ad serving metrics are impacted when integration testing mode is enabled. +- The `__ybot_test` cookie expires in 24 hours. +- It is good practice to click "Stop testing" when testing is complete, to return to normal ad delivery. + +For reference, the test bidder configuration below is included in the following manual test/example file [test/spec/e2e/gpt-examples/gpt_yieldbot.html](../test/spec/e2e/gpt-examples/gpt_yieldbot.html) +- Replace the adUnit `code` values with your respective DFP adUnitCode. +- ***Remember*** to **Enable** Yieldbot testing mode to force a bid to be returned. + +```javascript +var adUnit0 = { + code: '/00000000/leaderboard', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + } + } + ] +}; + +var adUnit1 = { + code: '/00000000/medium-rectangle', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] +}; + +var adUnit2 = { + code: '/00000000/large-rectangle', + mediaTypes: { + banner: { + sizes: [[300, 600]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'sidebar' + } + } + ] +}; + +var adUnit3 = { + code: '/00000000/skyscraper', + mediaTypes: { + banner: { + sizes: [[160, 600]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' + } + } + ] +}; +``` + +# Yieldbot Query Parameters + +| Name | Description | +| :--- | :---------- | +| `apie` | Yieldbot error description parameter | +| `bt` | Yieldbot bid request type: `initial` or `refresh` | +| `cts_ad` | Yieldbot ad creative request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_imp` | Yieldbot ad impression request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_ini` | Yieldbot bid request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_js` | Adapter code interpreting started timestamp, in milliseconds since the UNIX epoch | +| `cts_ns` | Performance timing navigationStart | +| `cts_rend` | Yieldbot ad creative render started timestamp, in milliseconds since the UNIX epoch | +| `cts_res` | Yieldbot bid response processing started timestamp, in milliseconds since the UNIX epoch | +| `e` | Yieldbot search parameters terminator | +| `ioa` | Indicator that the user-agent supports the Intersection Observer API | +| `it` | Indicator to specify Yieldbot creative rendering occured in an iframe: same/cross origin (`so`)/(`co`) or top (`none`) | +| `la` | Language and locale of the user-agent | +| `lo` | The page visit location Url | +| `lpv` | Time in milliseconds since the last page visit | +| `lpvi` | Pageview identifier for the last pageview within the session TTL | +| `np` | User-agent browsing platform | +| `pvd` | Counter for page visits within a session | +| `pvi` | Page visit identifier | +| `r` | The referring page Url | +| `ri` | Yieldbot ad request identifier | +| `sb` | Yieldbot ads blocked by user opt-out or suspicious activity detected during session | +| `sd` | User-agent screen dimensions | +| `si` | Publisher site visit session identifier | +| `slot` | Slot name for Yieldbot ad markup request e.g. `:x` | +| `sn` | Yieldbot bid slot names | +| `ssz` | Dimensions for the respective bid slot names | +| `to` | Number of hours offset from UTC | +| `ua` | User-Agent string | +| `v` | The version of the YieldbotAdapter | +| `vi` | First party user identifier | + + +# First-party Cookies + +| Name | Description | +| :--- | :---------- | +| `__ybotn` | The session is temporarily suspended from the ad server e.g. User-Agent, Geo location or suspicious activity | +| `__ybotu` | The Yieldbot first-party user identifier | +| `__ybotsi` | The user session identifier | +| `__ybotpvd` | The session pageview depth | +| `__ybotlpvi` | The last pageview identifier within the session | +| `__ybotlpv` | The time in **[ms]** since the last visit within the session | +| `__ybotc` | Geo/IP proximity location request Url | diff --git a/package.json b/package.json index f7cc918892d..663e89ea7e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.5.0-pre", + "version": "1.6.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -43,7 +43,7 @@ "eslint-plugin-standard": "^3.0.1", "faker": "^3.1.0", "fs.extra": "^1.3.2", - "gulp": "^3.8.7", + "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 10315241c40..b9930c3f363 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -173,13 +173,16 @@ export function newBidder(spec) { // register any required usersync pixels. const responses = []; function afterAllResponses(bids) { - const videoBid = bids && bids[0] && bids[0].mediaType && bids[0].mediaType === 'video'; + const bidsArray = bids ? (bids[0] ? bids : [bids]) : []; + + const videoBid = bidsArray.some(bid => bid.mediaType === 'video'); const cacheEnabled = config.getConfig('cache.url'); // video bids with cache enabled need to be cached first before they are considered done if (!(videoBid && cacheEnabled)) { done(); } + registerSyncs(responses); } diff --git a/src/auction.js b/src/auction.js index ec82c09b606..b4b9fe71669 100644 --- a/src/auction.js +++ b/src/auction.js @@ -395,20 +395,24 @@ export function getStandardBidderSettings() { } export function getKeyValueTargetingPairs(bidderCode, custBidObj) { + if (!custBidObj) { + return {}; + } + var keyValues = {}; var bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; // 1) set the keys from "standard" setting or from prebid defaults - if (custBidObj && bidder_settings) { + if (bidder_settings) { // initialize default if not set const standardSettings = getStandardBidderSettings(); setKeys(keyValues, standardSettings, custBidObj); - } - // 2) set keys from specific bidder setting override if they exist - if (bidderCode && custBidObj && bidder_settings && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - setKeys(keyValues, bidder_settings[bidderCode], custBidObj); - custBidObj.sendStandardTargeting = bidder_settings[bidderCode].sendStandardTargeting; + // 2) set keys from specific bidder setting override if they exist + if (bidderCode && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + setKeys(keyValues, bidder_settings[bidderCode], custBidObj); + custBidObj.sendStandardTargeting = bidder_settings[bidderCode].sendStandardTargeting; + } } // set native key value targeting diff --git a/test/.eslintrc.js b/test/.eslintrc.js index c1d36996a40..4cd5a854ac4 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -36,7 +36,5 @@ module.exports = { "no-use-before-define": "off", "no-useless-escape": "off", "one-var": "off", - "standard/array-bracket-even-spacing": "off", - "standard/object-curly-even-spacing": "off" } }; diff --git a/test/helpers/index_adapter_utils.js b/test/helpers/index_adapter_utils.js index 716ec1ff4f3..f01145b573d 100644 --- a/test/helpers/index_adapter_utils.js +++ b/test/helpers/index_adapter_utils.js @@ -203,7 +203,7 @@ exports.matchBidsOnSID = function(lhs, rhs) { var compared = compareOnKeys(lstore, rstore); var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); - return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched}; + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched }; } exports.matchBidsOnSize = function(lhs, rhs) { @@ -220,12 +220,12 @@ exports.matchBidsOnSize = function(lhs, rhs) { } var lstore = createObjectFromArray(configured); - var rstore = createObjectFromArray(rhs.map(bid => [ bid.banner.w + 'x' + bid.banner.h, bid])); + var rstore = createObjectFromArray(rhs.map(bid => [ bid.banner.w + 'x' + bid.banner.h, bid ])); var compared = compareOnKeys(lstore, rstore); var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); - return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched}; + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched }; } exports.getBidResponse = function(configuredBids, urlJSON, optionalPriceLevel, optionalResponseIdentifier, optionalPassOnBid, optionalResponseParam) { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index d52b5cfbd98..565029434f5 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -24,6 +24,87 @@ function timestamp() { return new Date().getTime(); } +const BIDDER_CODE = 'sampleBidder'; +const BIDDER_CODE1 = 'sampleBidder1'; + +const ADUNIT_CODE = 'adUnit-code'; +const ADUNIT_CODE1 = 'adUnit-code-1'; + +function mockBid(opts) { + let bidderCode = opts && opts.bidderCode; + + return { + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'bidderCode': bidderCode || BIDDER_CODE, + 'requestId': utils.getUniqueIdentifierStr(), + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; +} + +function mockBidRequest(bid, opts) { + if (!bid) { + throw new Error('bid required'); + } + let bidderCode = opts && opts.bidderCode; + let adUnitCode = opts && opts.adUnitCode; + + let requestId = utils.getUniqueIdentifierStr(); + + return { + 'bidderCode': bidderCode || bid.bidderCode, + 'auctionId': '20882439e3238c', + 'bidderRequestId': requestId, + 'bids': [ + { + 'bidder': bidderCode || bid.bidderCode, + 'params': { + 'placementId': 'id' + }, + 'adUnitCode': adUnitCode || ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + 'bidId': bid.requestId, + 'bidderRequestId': requestId, + 'auctionId': '20882439e3238c' + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }; +} + +function mockBidder(bidderCode, bids) { + let spec = { + code: bidderCode, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + getUserSyncs: sinon.stub() + }; + + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bids); + + return spec; +} + +const TEST_BIDS = [mockBid()]; +const TEST_BID_REQS = TEST_BIDS.map(mockBidRequest); + +function mockAjaxBuilder() { + return function(url, callback) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callback.success('response body', { getResponseHeader: fakeResponse }); + }; +} + describe('auctionmanager.js', function () { let xhr; @@ -36,53 +117,58 @@ describe('auctionmanager.js', function () { }); describe('getKeyValueTargetingPairs', function () { - var bid = {}; - var bidPriceCpm = 5.578; - var bidPbLg = 5.50; - var bidPbMg = 5.50; - var bidPbHg = 5.57; - var bidPbAg = 5.50; + const DEFAULT_BID = { + cpm: 5.578, + pbLg: 5.50, + pbMg: 5.50, + pbHg: 5.57, + pbAg: 5.50, + + height: 300, + width: 250, + getSize() { + return this.height + 'x' + this.width; + }, + + adUnitCode: '12345', + bidderCode: 'appnexus', + adId: '1adId', + source: 'client', + mediaType: 'banner', + timeToRespond: 123, + }; + + /* return the expected response for a given bid, filter by keys if given */ + function getDefaultExpected(bid, keys) { + var expected = { + 'hb_bidder': bid.bidderCode, + 'hb_adid': bid.adId, + 'hb_pb': bid.pbMg, + 'hb_size': bid.getSize(), + 'hb_source': bid.source, + 'hb_format': bid.mediaType, + 'hb_ttr': bid.timeToRespond, + }; + + if (!keys) { + return expected; + } - var adUnitCode = '12345'; - var bidderCode = 'appnexus'; - var size = '300x250'; - var adId = '1adId'; - var source = 'client'; - var mediatype = 'banner'; + return keys.reduce((map, key) => { + map[key] = expected[key]; + return map; + }, {}); + } - const timeToRespond = 123; + var bid = {}; before(function () { - bid.cpm = bidPriceCpm; - bid.pbLg = bidPbLg; - bid.pbMg = bidPbMg; - bid.pbHg = bidPbHg; - bid.pbAg = bidPbAg; - - bid.height = 300; - bid.width = 250; - bid.adUnitCode = adUnitCode; - bid.getSize = function () { - return this.height + 'x' + this.width; - }; - bid.bidderCode = bidderCode; - bid.adId = adId; - bid.source = source; - bid.mediaType = mediatype; - bid.timeToRespond = timeToRespond; + bid = Object.assign({}, DEFAULT_BID); }); it('No bidder level configuration defined - default', function () { - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbMg, - 'hb_size': size, - 'hb_source': source, - 'hb_format': mediatype, - 'hb_ttr': timeToRespond, - }; - var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + var expected = getDefaultExpected(bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); assert.deepEqual(response, expected); }); @@ -125,20 +211,21 @@ describe('auctionmanager.js', function () { return bidResponse.mediaType; } }, + { + key: 'hb_ttr', + val: function (bidResponse) { + return bidResponse.timeToRespond; + } + }, ] } }; - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbHg, - 'hb_size': size, - 'hb_source': source, - 'hb_format': mediatype, - }; - var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + var expected = getDefaultExpected(bid); + expected.hb_pb = bid.pbHg; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); @@ -174,16 +261,10 @@ describe('auctionmanager.js', function () { } }; - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbHg, - 'hb_size': size, - 'hb_source': source, - 'hb_format': mediatype, - 'hb_ttr': timeToRespond, - }; - var response = getKeyValueTargetingPairs(bidderCode, bid); + var expected = getDefaultExpected(bid); + expected.hb_pb = bid.pbHg; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); @@ -218,17 +299,9 @@ describe('auctionmanager.js', function () { } }; + var expected = getDefaultExpected(bid); - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbMg, - 'hb_size': size, - 'hb_source': source, - 'hb_format': mediatype, - 'hb_ttr': timeToRespond - }; - var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); @@ -266,9 +339,10 @@ describe('auctionmanager.js', function () { } }; + var expected = getDefaultExpected(bid, ['hb_bidder', 'hb_adid']); + expected.hb_pb = 10.0; - var expected = { 'hb_bidder': bidderCode, 'hb_adid': adId, 'hb_pb': 10.0 }; - var response = getKeyValueTargetingPairs(bidderCode, bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); @@ -349,14 +423,10 @@ describe('auctionmanager.js', function () { } }; + var expected = getDefaultExpected(bid, ['hb_bidder', 'hb_adid', 'hb_size']); + expected.hb_pb = 15.0; - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': 15.0, - 'hb_size': '300x250' - }; - var response = getKeyValueTargetingPairs(bidderCode, bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); @@ -385,17 +455,10 @@ describe('auctionmanager.js', function () { ] } }; + var expected = getDefaultExpected(bid); + expected.hb_pb = 5.57; - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': 5.57, - 'hb_size': '300x250', - 'hb_source': source, - 'hb_format': mediatype, - 'hb_ttr': timeToRespond, - }; - var response = getKeyValueTargetingPairs(bidderCode, bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); assert.equal(bid.sendStandardTargeting, false); }); @@ -422,7 +485,7 @@ describe('auctionmanager.js', function () { 'aKeyWithAValue': 42 }; - var response = getKeyValueTargetingPairs(bidderCode, bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); }); }); @@ -483,54 +546,13 @@ describe('auctionmanager.js', function () { let spec; let auction; let ajaxStub; - const BIDDER_CODE = 'sampleBidder'; - const BIDDER_CODE1 = 'sampleBidder1'; + let bids = TEST_BIDS; let makeRequestsStub; - let bids = [{ - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'bidderCode': BIDDER_CODE, - 'requestId': '4d0a6829338a07', - 'creativeId': 'id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 - }]; - - let bidRequests = [{ - 'bidderCode': BIDDER_CODE, - 'auctionId': '20882439e3238c', - 'bidderRequestId': '331f3cf3f1d9c8', - 'bids': [ - { - 'bidder': BIDDER_CODE, - 'params': { - 'placementId': 'id' - }, - 'adUnitCode': 'adUnit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4d0a6829338a07', - 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c' - } - ], - 'auctionStart': 1505250713622, - 'timeout': 3000 - }]; before(() => { makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); - makeRequestsStub.returns(bidRequests); - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { - return function(url, callback) { - const fakeResponse = sinon.stub(); - fakeResponse.returns('headerContent'); - callback.success('response body', { getResponseHeader: fakeResponse }); - } - }); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); }); after(() => { @@ -540,28 +562,27 @@ describe('auctionmanager.js', function () { describe('when auction timeout is 3000', () => { let loadScriptStub; + before(() => { + makeRequestsStub.returns(TEST_BID_REQS); + }); beforeEach(() => { adUnits = [{ - code: 'adUnit-code', + code: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }]; - adUnitCodes = ['adUnit-code']; + adUnitCodes = [ADUNIT_CODE]; auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); - spec = { - code: BIDDER_CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { args[1](); }); + + spec = mockBidder(BIDDER_CODE, bids); + registerBidder(spec); }); afterEach(() => { @@ -569,57 +590,32 @@ describe('auctionmanager.js', function () { loadScriptStub.restore(); }); - it('should return proper price bucket increments for dense mode when cpm is in range 0-3', () => { - bids[0].cpm = '1.99'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.pbDg, '1.99', '0 - 3 hits at to 1 cent increment'); - }); + function checkPbDg(cpm, expected, msg) { + return function() { + bids[0].cpm = cpm; + auction.callBids(); - it('should return proper price bucket increments for dense mode when cpm is in range 3-8', () => { - bids[0].cpm = '4.39'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.pbDg, '4.35', '3 - 8 hits at 5 cent increment'); - }); + let registeredBid = auction.getBidsReceived().pop(); + assert.equal(registeredBid.pbDg, expected, msg); + }; + }; - it('should return proper price bucket increments for dense mode when cpm is in range 8-20', () => { - bids[0].cpm = '19.99'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.pbDg, '19.50', '8 - 20 hits at 50 cent increment'); - }); + it('should return proper price bucket increments for dense mode when cpm is in range 0-3', + checkPbDg('1.99', '1.99', '0 - 3 hits at to 1 cent increment')); - it('should return proper price bucket increments for dense mode when cpm is 20+', () => { - bids[0].cpm = '73.07'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.pbDg, '20.00', '20+ caps at 20.00'); - }); + it('should return proper price bucket increments for dense mode when cpm is in range 3-8', + checkPbDg('4.39', '4.35', '3 - 8 hits at 5 cent increment')); + + it('should return proper price bucket increments for dense mode when cpm is in range 8-20', + checkPbDg('19.99', '19.50', '8 - 20 hits at 50 cent increment')); + + it('should return proper price bucket increments for dense mode when cpm is 20+', + checkPbDg('73.07', '20.00', '20+ caps at 20.00')); it('should place dealIds in adserver targeting', () => { bids[0].dealId = 'test deal'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); auction.callBids(); + let registeredBid = auction.getBidsReceived().pop(); assert.equal(registeredBid.adserverTargeting[`hb_deal`], 'test deal', 'dealId placed in adserverTargeting'); }); @@ -627,43 +623,22 @@ describe('auctionmanager.js', function () { it('should pass through default adserverTargeting sent from adapter', () => { bids[0].adserverTargeting = {}; bids[0].adserverTargeting.extra = 'stuff'; - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); auction.callBids(); + let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.adserverTargeting.hb_bidder, 'sampleBidder'); + assert.equal(registeredBid.adserverTargeting.hb_bidder, BIDDER_CODE); assert.equal(registeredBid.adserverTargeting.extra, 'stuff'); }); it('installs publisher-defined renderers on bids', () => { - let bidRequests = [{ - 'bidderCode': BIDDER_CODE, - 'auctionId': '20882439e3238c', - 'bidderRequestId': '331f3cf3f1d9c8', - 'bids': [ - { - 'bidder': BIDDER_CODE, - 'params': { - 'placementId': 'id' - }, - 'adUnitCode': 'adUnit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4d0a6829338a07', - 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c', - 'renderer': { - url: 'renderer.js', - render: (bid) => bid - } - } - ], - 'auctionStart': 1505250713622, - 'timeout': 3000 - }]; - + let renderer = { + url: 'renderer.js', + render: (bid) => bid + }; + let bidRequests = [Object.assign({}, TEST_BID_REQS[0])]; + bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]); makeRequestsStub.returns(bidRequests); + let bids1 = Object.assign({}, bids[0], { @@ -671,15 +646,72 @@ describe('auctionmanager.js', function () { mediaType: 'video-outstream', } ); - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); spec.interpretResponse.returns(bids1); auction.callBids(); const addedBid = auction.getBidsReceived().pop(); assert.equal(addedBid.renderer.url, 'renderer.js'); }); }); + + describe('when auction timeout is 20', () => { + let loadScriptStub; + let eventsEmitSpy; + let getBidderRequestStub; + + before(() => { + bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; + let bidRequests = bids.map(bid => mockBidRequest(bid)); + + makeRequestsStub.returns(bidRequests); + }); + + beforeEach(() => { + adUnits = [{ + code: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ] + }]; + adUnitCodes = [ADUNIT_CODE]; + + auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 20}); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + + loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { + args[1](); + }); + + spec = mockBidder(BIDDER_CODE, [bids[0]]); + registerBidder(spec); + + // Timeout is checked when bid is received. If that bid is the only one + // auction is waiting for, timeout is not emitted, so we need to add a + // second bidder to get timeout event. + let spec1 = mockBidder(BIDDER_CODE1, [bids[1]]); + registerBidder(spec1); + + eventsEmitSpy = sinon.spy(events, 'emit'); + + let origGBR = utils.getBidderRequest; + getBidderRequestStub = sinon.stub(utils, 'getBidderRequest'); + getBidderRequestStub.callsFake((bidRequests, bidder, adUnitCode) => { + let req = origGBR(bidRequests, bidder, adUnitCode); + req.start = 1000; + return req; + }); + }); + afterEach(() => { + auctionModule.newAuction.restore(); + loadScriptStub.restore(); + events.emit.restore(); + getBidderRequestStub.restore(); + }); + it('should emit BID_TIMEOUT for timed out bids', () => { + auction.callBids(); + assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); + }); + }); }); describe('addBidResponse', () => { @@ -690,87 +722,19 @@ describe('auctionmanager.js', function () { let spec1; let auction; let ajaxStub; - const BIDDER_CODE = 'sampleBidder'; - const BIDDER_CODE1 = 'sampleBidder1'; - let makeRequestsStub; - let bids = [{ - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'bidderCode': BIDDER_CODE, - 'requestId': '4d0a6829338a07', - 'creativeId': 'id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 - }]; - - let bids1 = [{ - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'bidderCode': BIDDER_CODE1, - 'requestId': '5d0a6829338a07', - 'creativeId': 'id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 - }]; - - let bidRequests = [{ - 'bidderCode': BIDDER_CODE, - 'auctionId': '20882439e3238c', - 'bidderRequestId': '331f3cf3f1d9c8', - 'bids': [ - { - 'bidder': BIDDER_CODE, - 'params': { - 'placementId': 'id' - }, - 'adUnitCode': 'adUnit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4d0a6829338a07', - 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c' - } - ], - 'auctionStart': 1505250713622, - 'timeout': 3000 - }, { - 'bidderCode': BIDDER_CODE1, - 'auctionId': '20882439e3238c', - 'bidderRequestId': '661f3cf3f1d9c8', - 'bids': [ - { - 'bidder': BIDDER_CODE1, - 'params': { - 'placementId': 'id' - }, - 'adUnitCode': 'adUnit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '5d0a6829338a07', - 'bidderRequestId': '661f3cf3f1d9c8', - 'auctionId': '20882439e3238c' - } - ], - 'auctionStart': 1505250713623, - 'timeout': 3000 - }]; + let bids = TEST_BIDS; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; before(() => { - makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); + let bidRequests = [ + mockBidRequest(bids[0]), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }) + ]; + let makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); makeRequestsStub.returns(bidRequests); - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { - return function(url, callback) { - const fakeResponse = sinon.stub(); - fakeResponse.returns('headerContent'); - callback.success('response body', { getResponseHeader: fakeResponse }); - } - }); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); }); after(() => { @@ -780,36 +744,26 @@ describe('auctionmanager.js', function () { beforeEach(() => { adUnits = [{ - code: 'adUnit-code', + code: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }, { - code: 'adUnit-code-1', + code: ADUNIT_CODE1, bids: [ {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, ] }]; - adUnitCodes = ['adUnit-code', 'adUnit-code-1']; + adUnitCodes = adUnits.map(({ code }) => code); auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); - spec = { - code: BIDDER_CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; + spec = mockBidder(BIDDER_CODE, bids); + spec1 = mockBidder(BIDDER_CODE1, bids1); - spec1 = { - code: BIDDER_CODE1, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; + registerBidder(spec); + registerBidder(spec1); }); afterEach(() => { @@ -817,17 +771,6 @@ describe('auctionmanager.js', function () { }); it('should not alter bid adID', () => { - registerBidder(spec); - registerBidder(spec1); - - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - - spec1.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec1.isBidRequestValid.returns(true); - spec1.interpretResponse.returns(bids1); - auction.callBids(); const addedBid2 = auction.getBidsReceived().pop(); @@ -840,17 +783,6 @@ describe('auctionmanager.js', function () { bids1[0].width = undefined; bids1[0].height = undefined; - registerBidder(spec); - registerBidder(spec1); - - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - - spec1.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec1.isBidRequestValid.returns(true); - spec1.interpretResponse.returns(bids1); - auction.callBids(); let length = auction.getBidsReceived().length; @@ -860,21 +792,13 @@ describe('auctionmanager.js', function () { }); it('should run auction after video bids have been cached', () => { - sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123}]); + 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'})]; + 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(); @@ -885,5 +809,30 @@ describe('auctionmanager.js', function () { config.getConfig.restore(); store.store.restore(); }); + + it('runs auction after video responses with multiple bid objects 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' }), + Object.assign({}, bids[0], { mediaType: 'banner' }), + ]; + const bids1Copy = [ + Object.assign({}, bids1[0], { mediaType: 'video' }), + Object.assign({}, bids1[0], { mediaType: 'video' }), + ]; + + spec.interpretResponse.returns(bidsCopy); + spec1.interpretResponse.returns(bids1Copy); + + auction.callBids(); + + assert.equal(auction.getBidsReceived().length, 4); + assert.equal(auction.getAuctionStatus(), 'completed'); + + config.getConfig.restore(); + store.store.restore(); + }); }); }); diff --git a/test/spec/e2e/gpt-examples/gpt_yieldbot.html b/test/spec/e2e/gpt-examples/gpt_yieldbot.html new file mode 100644 index 00000000000..12766c537f6 --- /dev/null +++ b/test/spec/e2e/gpt-examples/gpt_yieldbot.html @@ -0,0 +1,241 @@ + + + + + + + + + + +
+
+ Yieldbot integration test mode: + +
    +
  • START (i.e.force bids to be returned)
  • +
  • STOP
  • +
+
+ +

Prebid.js Yieldbot Adapter Test

+
+
+ +
+

Lorem ipsum dolor. Sit amet proin. Integer cursus mi mus curabitur euismod vel quos duis bibendum nec interdum porta dolor a viverra nisl fusce. Volutpat sit at. Donec nisl taciti. Eget eu lobortis. Excepteur diam orci lacus nibh pharetra. Justo neque maecenas. Viverra molestie dolor ante rutrum vivamus libero urna suscipit leo praesent ultricies. In dignissim qui ante bibendum in. Habitasse ac arcu non nulla augue. Felis lectus non tempus in aliquam. Sit porttitor nec. Sodales non sit eu duis.

+
+ +
+

Donec feugiat ornare a amet optio. Vitae sit sapien. Vitae nec justo. Fusce ac in semper ligula duis eget vel sit. Augue mauris sit. A adipisicing orci est augue dapibus ullamcorper faucibus fermentum. Et phasellus in tempus vivamus praesent. Nisl dui porttitor. Iaculis vulputate eros ut interdum eu. Lacus quis magna varius in quis. Congue erat porttitor sit eu vitae pharetra scelerisque nec. Dolor dui vel ut velit vestibulum. Lectus ullamcorper mi. Curabitur ipsum pellentesque sed erat est est sapien in tempor sodales viverra. Dui volutpat morbi eleifend fringilla quis. Neque erat erat. Rhoncus sed posuere. Dapibus fusce ut lacus mus est pede sed quisquam. Quis aliquam pellentesque. Wisi ac odio eu wisi amet ut ipsum a erat aliquam nunc.

+

Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

+

Vivamus mauris metus. Ridiculus habitant lorem in nulla in quam eros ut. Libero aliquam platea. In enim consectetuer eget mattis accumsan aenean faucibus tincidunt. Amet donec vitae wisi pellentesque magna non lacinia qui. In erat in maecenas amet dui. Aliquam elit vel. Ligula sodales lacus. Nisl a purus. Pharetra velit porttitor vel vel non turpis viverra fringilla lorem arcu pellentesque sed aliquet nonummy quisque dapibus ullamcorper. Mollis ipsum nulla. Tempor tempus vitae. Luctus amet vel. Suspendisse sagittis vestibulum fusce eu.

+

Urna et felis bibendum felis sit vestibulum wisi pharetra quisque ac quis cursus suspendisse quisque aenean luctus curabitur. Eget nec leo. Mi placerat cras nulla et integer eget in sed. Non magni parturient. Egestas iaculis malesuada. Nec a duis. Pede condimentum ullamcorper. Augue arcu tellus. In velit in in duis odio dictum wisi proin quis eget sit. Felis tempus inceptos. Turpis risus eu. Mi vivamus consequat. Lectus dui imperdiet amet orci vehicula in vel pellentesque habitant suscipit aliquam. Proin porttitor vitae ultricies a in. Est duis pede. Tristique velit vestibulum odio sodales morbi magna ut vitae. Elementum imperdiet sodales ultrices tortor mollis vehicula lorem varius pellentesque mi ut sit turpis feugiat. In convallis urna. Justo aliquam sed quis scelerisque nonummy. Lobortis rhoncus ornare. Pellentesque leo quam at beatae in. Erat lorem tempus. Molestie faucibus a id mauris montes. Sed orci vulputate. Libero justo curabitur. Amet orci ante. Non felis est erat cras purus id at id nibh nunc facilisis amet metus sagittis pellentesque eros amet. Vitae sit nulla vitae wisi diam. Tincidunt ipsum eleifend semper tortor non. Tellus amet aliquet.

+

Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

+

Vivamus mauris metus. Ridiculus habitant lorem in nulla in quam eros ut. Libero aliquam platea. In enim consectetuer eget mattis accumsan aenean faucibus tincidunt. Amet donec vitae wisi pellentesque magna non lacinia qui. In erat in maecenas amet dui. Aliquam elit vel. Ligula sodales lacus. Nisl a purus. Pharetra velit porttitor vel vel non turpis viverra fringilla lorem arcu pellentesque sed aliquet nonummy quisque dapibus ullamcorper. Mollis ipsum nulla. Tempor tempus vitae. Luctus amet vel. Suspendisse sagittis vestibulum fusce eu.

+

Urna et felis bibendum felis sit vestibulum wisi pharetra quisque ac quis cursus suspendisse quisque aenean luctus curabitur. Eget nec leo. Mi placerat cras nulla et integer eget in sed. Non magni parturient. Egestas iaculis malesuada. Nec a duis. Pede condimentum ullamcorper. Augue arcu tellus. In velit in in duis odio dictum wisi proin quis eget sit. Felis tempus inceptos. Turpis risus eu. Mi vivamus consequat. Lectus dui imperdiet amet orci vehicula in vel pellentesque habitant suscipit aliquam. Proin porttitor vitae ultricies a in. Est duis pede. Tristique velit vestibulum odio sodales morbi magna ut vitae. Elementum imperdiet sodales ultrices tortor mollis vehicula lorem varius pellentesque mi ut sit turpis feugiat. In convallis urna. Justo aliquam sed quis scelerisque nonummy. Lobortis rhoncus ornare. Pellentesque leo quam at beatae in. Erat lorem tempus. Molestie faucibus a id mauris montes. Sed orci vulputate. Libero justo curabitur. Amet orci ante. Non felis est erat cras purus id at id nibh nunc facilisis amet metus sagittis pellentesque eros amet. Vitae sit nulla vitae wisi diam. Tincidunt ipsum eleifend semper tortor non. Tellus amet aliquet.

+
+
+ +
+
+ +
+
+

Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

+
+ + diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index a81c92b4a48..9c318e4bdd5 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -14,7 +14,7 @@ describe('33acrossBidAdapter:', function () { const SITE_ID = 'pub1234'; const PRODUCT_ID = 'product1'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; - const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch'; + const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html'; beforeEach(function() { this.bidRequests = [ diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 24249cc8c36..60016fbfb9e 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect} from 'chai'; +import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter'; describe('a4gAdapterTests', () => { diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 357e6e67f4d..a330f18e685 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -1,6 +1,7 @@ import {assert, expect} from 'chai'; import * as url from 'src/url'; import {spec} from 'modules/adformBidAdapter'; +import { BANNER, VIDEO } from 'src/mediaTypes'; describe('Adform adapter', () => { let serverResponse, bidRequest, bidResponses; @@ -29,7 +30,7 @@ describe('Adform adapter', () => { it('should pass multiple bids via single request', () => { let request = spec.buildRequests(bids); let parsedUrl = parseUrl(request.url); - assert.lengthOf(parsedUrl.items, 3); + assert.lengthOf(parsedUrl.items, 5); }); it('should handle global request parameters', () => { @@ -64,6 +65,16 @@ describe('Adform adapter', () => { transactionId: '5f33781f-9552-4iuy', priceType: 'gross' }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + }, { mid: '3', pdom: 'home', @@ -81,7 +92,7 @@ describe('Adform adapter', () => { describe('interpretResponse', () => { it('should respond with empty response when there is empty serverResponse', () => { - let result = spec.interpretResponse({ body: {}}, {}); + let result = spec.interpretResponse({ body: {} }, {}); assert.deepEqual(result, []); }); it('should respond with empty response when sizes doesn\'t match', () => { @@ -135,14 +146,31 @@ describe('Adform adapter', () => { it('should create bid response item for every requested item', () => { let result = spec.interpretResponse(serverResponse, bidRequest); - assert.lengthOf(result, 3); + assert.lengthOf(result, 5); + }); + + it('should create bid response with vast xml', () => { + const result = spec.interpretResponse(serverResponse, bidRequest)[3]; + assert.equal(result.vastXml, ''); + }); + + it('should create bid response with vast url', () => { + const result = spec.interpretResponse(serverResponse, bidRequest)[4]; + assert.equal(result.vastUrl, 'vast://url'); + }); + + it('should set mediaType on bid response', () => { + const expected = [ BANNER, BANNER, BANNER, VIDEO, VIDEO ]; + const result = spec.interpretResponse(serverResponse, bidRequest); + for (let i = 0; i < result.length; i++) { + assert.equal(result[i].mediaType, expected[i]); + } }); }); beforeEach(() => { let sizes = [[250, 300], [300, 250], [300, 600]]; - let adUnitCode = ['div-01', 'div-02', 'div-03']; - let placementCode = adUnitCode; + let placementCode = ['div-01', 'div-02', 'div-03', 'div-04', 'div-05']; let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', priceType: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }]; bids = [ { @@ -179,6 +207,28 @@ describe('Adform adapter', () => { placementCode: placementCode[2], sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[3], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[4], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' } ]; serverResponse = { @@ -209,6 +259,24 @@ describe('Adform adapter', () => { width: 600, win_bid: 10, win_cur: 'EUR' + }, + { + deal_id: '123abc', + height: 300, + response: 'vast_content', + width: 600, + win_bid: 10, + win_cur: 'EUR', + vast_content: '' + }, + { + deal_id: '123abc', + height: 300, + response: 'vast_url', + width: 600, + win_bid: 10, + win_cur: 'EUR', + vast_url: 'vast://url' } ], headers: {} diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 661d6a63e3d..7038e2e572c 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -181,7 +181,7 @@ describe('AppNexusAdapter', () => { nativeParams: { title: {required: true}, body: {required: true}, - image: {required: true, sizes: [{ width: 100, height: 100 }] }, + image: {required: true, sizes: [{ width: 100, height: 100 }]}, cta: {required: false}, sponsoredBy: {required: true} } @@ -194,7 +194,7 @@ describe('AppNexusAdapter', () => { expect(payload.tags[0].native.layouts[0]).to.deep.equal({ title: {required: true}, description: {required: true}, - main_image: {required: true, sizes: [{ width: 100, height: 100 }] }, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, ctatext: {required: false}, sponsored_by: {required: true} }); @@ -215,7 +215,7 @@ describe('AppNexusAdapter', () => { const payload = JSON.parse(request.data); expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - main_image: {required: true, sizes: [{}] }, + main_image: {required: true, sizes: [{}]}, }); }); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js index 6b21eb459d4..d92597c913c 100644 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js @@ -92,6 +92,17 @@ describe('AudienceNetwork adapter', () => { })).to.equal(true); }); + it('native with non-IAB size', () => { + expect(isBidRequestValid({ + bidder, + sizes: [[728, 90]], + params: { + placementId, + format: 'native' + } + })).to.equal(true); + }); + it('video', () => { expect(isBidRequestValid({ bidder, @@ -139,6 +150,44 @@ describe('AudienceNetwork adapter', () => { data: 'placementids[]=test-placement-id&adformats[]=video&testmode=false&pageurl=&sdk[]=&playerwidth=640&playerheight=480' }]); }); + + it('can build URL for native unit in non-IAB size', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[728, 90]], + params: { + placementId, + format: 'native' + } + }])).to.deep.equal([{ + adformats: ['native'], + method: 'GET', + requestIds: [requestId], + sizes: ['728x90'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: 'placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=&sdk[]=5.5.web' + }]); + }); + + it('can build URL for fullwidth 300x250 unit', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[300, 250]], + params: { + placementId, + format: 'fullwidth' + } + }])).to.deep.equal([{ + adformats: ['fullwidth'], + method: 'GET', + requestIds: [requestId], + sizes: ['300x250'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: 'placementids[]=test-placement-id&adformats[]=fullwidth&testmode=false&pageurl=&sdk[]=5.5.web' + }]); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 92e16573972..24b97306388 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,31 +1,46 @@ import { expect } from 'chai'; -import { spec, ENDPOINT } from 'modules/beachfrontBidAdapter'; +import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT } from 'modules/beachfrontBidAdapter'; import * as utils from 'src/utils'; describe('BeachfrontAdapter', () => { - let bidRequest; + let bidRequests; beforeEach(() => { - bidRequest = { - bidder: 'beachfront', - params: { - bidfloor: 5.00, - appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' - }, - adUnitCode: 'adunit-code', - sizes: [ 640, 480 ], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475' - }; + bidRequests = [ + { + bidder: 'beachfront', + params: { + bidfloor: 2.00, + appId: '3b16770b-17af-4d22-daff-9606bdf2c9c3' + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + sizes: [ 300, 250 ], + bidId: '25186806a41eab', + bidderRequestId: '15bdd8d4a0ebaf', + auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309' + }, { + bidder: 'beachfront', + params: { + bidfloor: 1.00, + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }, + adUnitCode: 'div-gpt-ad-1460505748561-1', + sizes: [ 300, 600 ], + bidId: '365088ee6d649d', + bidderRequestId: '15bdd8d4a0ebaf', + auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309' + } + ]; }); describe('spec.isBidRequestValid', () => { it('should return true when the required params are passed', () => { + const bidRequest = bidRequests[0]; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); it('should return false when the "bidfloor" param is missing', () => { + const bidRequest = bidRequests[0]; bidRequest.params = { appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' }; @@ -33,6 +48,7 @@ describe('BeachfrontAdapter', () => { }); it('should return false when the "appId" param is missing', () => { + const bidRequest = bidRequests[0]; bidRequest.params = { bidfloor: 5.00 }; @@ -40,6 +56,7 @@ describe('BeachfrontAdapter', () => { }); it('should return false when no bid params are passed', () => { + const bidRequest = bidRequests[0]; bidRequest.params = {}; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); @@ -51,98 +68,254 @@ describe('BeachfrontAdapter', () => { }); describe('spec.buildRequests', () => { - it('should create a POST request for every bid', () => { - const requests = spec.buildRequests([ bidRequest ]); - expect(requests[0].method).to.equal('POST'); - expect(requests[0].url).to.equal(ENDPOINT + bidRequest.params.appId); - }); + describe('for video bids', () => { + it('should attach the bid request object', () => { + bidRequests[0].mediaTypes = { video: {} }; + bidRequests[1].mediaTypes = { video: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests[0].bidRequest).to.equal(bidRequests[0]); + expect(requests[1].bidRequest).to.equal(bidRequests[1]); + }); - it('should attach the bid request object', () => { - const requests = spec.buildRequests([ bidRequest ]); - expect(requests[0].bidRequest).to.equal(bidRequest); - }); + it('should create a POST request for each bid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(VIDEO_ENDPOINT + bidRequest.params.appId); + }); - it('should attach request data', () => { - const requests = spec.buildRequests([ bidRequest ]); - const data = requests[0].data; - const [ width, height ] = bidRequest.sizes; - expect(data.isPrebid).to.equal(true); - expect(data.appId).to.equal(bidRequest.params.appId); - expect(data.domain).to.equal(document.location.hostname); - expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); - expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); - expect(data.site).to.deep.equal({ page: utils.getTopWindowLocation().host }); - expect(data.device).to.deep.contain({ ua: navigator.userAgent }); - expect(data.cur).to.deep.equal(['USD']); - }); + it('should attach request data', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + expect(data.isPrebid).to.equal(true); + expect(data.appId).to.equal(bidRequest.params.appId); + expect(data.domain).to.equal(document.location.hostname); + expect(data.id).to.be.a('string'); + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.site).to.deep.equal({ page: utils.getTopWindowLocation().host }); + expect(data.device).to.deep.contain({ ua: navigator.userAgent }); + expect(data.cur).to.deep.equal(['USD']); + }); - it('must parse bid size from a nested array', () => { - const width = 640; - const height = 480; - bidRequest.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ bidRequest ]); - const data = requests[0].data; - expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); - }); + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.sizes = [[ width, height ]]; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + }); - it('must parse bid size from a string', () => { - const width = 640; - const height = 480; - bidRequest.sizes = `${width}x${height}`; - const requests = spec.buildRequests([ bidRequest ]); - const data = requests[0].data; - expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.sizes = `${width}x${height}`; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + }); + + it('must handle an empty bid size', () => { + const bidRequest = bidRequests[0]; + bidRequest.sizes = []; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: undefined, h: undefined }); + }); }); - it('must handle an empty bid size', () => { - bidRequest.sizes = []; - const requests = spec.buildRequests([ bidRequest ]); - const data = requests[0].data; - expect(data.imp[0].video).to.deep.equal({ w: undefined, h: undefined }); + describe('for banner bids', () => { + it('should attach the bid requests array', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests[0].bidRequest).to.deep.equal(bidRequests); + }); + + it('should create a single POST request for all bids', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(BANNER_ENDPOINT); + }); + + it('should attach request data', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + const topLocation = utils.getTopWindowLocation(); + expect(data.slots).to.deep.equal([ + { + slot: bidRequest.adUnitCode, + id: bidRequest.params.appId, + bidfloor: bidRequest.params.bidfloor, + sizes: [{ w: width, h: height }] + } + ]); + expect(data.page).to.equal(topLocation.href); + expect(data.domain).to.equal(topLocation.hostname); + expect(data.search).to.equal(topLocation.search); + expect(data.ua).to.equal(navigator.userAgent); + }); + + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.sizes = [[ width, height ]]; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([ + { w: width, h: height } + ]); + }); + + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.sizes = `${width}x${height}`; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([ + { w: width, h: height } + ]); + }); + + it('must handle an empty bid size', () => { + const bidRequest = bidRequests[0]; + bidRequest.sizes = []; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([]); + }); }); }); describe('spec.interpretResponse', () => { - it('should return no bids if the response is not valid', () => { - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); + describe('for video bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - it('should return no bids if the response "url" is missing', () => { - const serverResponse = { - bidPrice: 5.00 - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); + it('should return no bids if the response "url" is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const serverResponse = { + bidPrice: 5.00 + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - it('should return no bids if the response "bidPrice" is missing', () => { - const serverResponse = { - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse.length).to.equal(0); + it('should return no bids if the response "bidPrice" is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const serverResponse = { + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + cmpId: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const [ width, height ] = bidRequest.sizes; + expect(bidResponse).to.deep.equal({ + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.bidPrice, + creativeId: serverResponse.cmpId, + vastUrl: serverResponse.url, + width: width, + height: height, + mediaType: 'video', + currency: 'USD', + netRevenue: true, + ttl: 300 + }); + }); }); - it('should return a valid bid response', () => { - const serverResponse = { - bidPrice: 5.00, - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse).to.deep.equal({ - requestId: bidRequest.bidId, - bidderCode: spec.code, - cpm: serverResponse.bidPrice, - creativeId: serverResponse.cmpId, - vastUrl: serverResponse.url, - width: 640, - height: 480, - mediaType: 'video', - currency: 'USD', - ttl: 300, - netRevenue: true + describe('for banner bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response is empty', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return valid banner bid responses', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[0].sizes = [[ 300, 250 ], [ 728, 90 ]]; + bidRequests[1].mediaTypes = { banner: {} }; + bidRequests[1].sizes = [[ 300, 600 ], [ 200, 200 ]]; + const serverResponse = [{ + slot: bidRequests[0].adUnitCode, + adm: '
', + crid: 'crid_1', + price: 3.02, + w: 728, + h: 90 + }, { + slot: bidRequests[1].adUnitCode, + adm: '
', + crid: 'crid_2', + price: 3.06, + w: 300, + h: 600 + }]; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest: bidRequests }); + expect(bidResponse.length).to.equal(2); + for (let i = 0; i < bidRequests.length; i++) { + expect(bidResponse[ i ]).to.deep.equal({ + requestId: bidRequests[ i ].bidId, + bidderCode: spec.code, + ad: serverResponse[ i ].adm, + creativeId: serverResponse[ i ].crid, + cpm: serverResponse[ i ].price, + width: serverResponse[ i ].w, + height: serverResponse[ i ].h, + mediaType: 'banner', + currency: 'USD', + netRevenue: true, + ttl: 300 + }); + } }); }); }); diff --git a/test/spec/modules/fairtradeBidAdapter_spec.js b/test/spec/modules/fairtradeBidAdapter_spec.js new file mode 100644 index 00000000000..07c26e8f0c1 --- /dev/null +++ b/test/spec/modules/fairtradeBidAdapter_spec.js @@ -0,0 +1,289 @@ +import { expect } from 'chai'; +import { spec } from 'modules/fairtradeBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('FairTradeAdapter', function () { + 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': 'fairtrade', + 'params': { + 'uid': '166' + }, + '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 = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '167' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '165'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 165, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 166, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 167, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '166' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 166, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '167' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '168' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '169' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js new file mode 100644 index 00000000000..646448bd5a7 --- /dev/null +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -0,0 +1,249 @@ +import { expect } from 'chai'; +import { spec } from 'modules/invibesBidAdapter'; + +describe('invibesBidAdapter:', function () { + const BIDDER_CODE = 'invibes'; + const PLACEMENT_ID = '12345'; + const ENDPOINT = '//bid.videostep.com/Bid/VideoAdContent'; + const SYNC_ENDPOINT = '//k.r66net.com/GetUserSync'; + + let bidRequests = [ + { + bidId: 'b1', + bidder: BIDDER_CODE, + bidderRequestId: 'r1', + params: { + placementId: PLACEMENT_ID + }, + adUnitCode: 'test-div', + auctionId: 'a1', + sizes: [ + [300, 250], + [400, 300], + [125, 125] + ], + transactionId: 't1' + }, { + bidId: 'b2', + bidder: BIDDER_CODE, + bidderRequestId: 'r2', + params: { + placementId: 'abcde' + }, + adUnitCode: 'test-div', + auctionId: 'a2', + sizes: [ + [300, 250], + [400, 300] + ], + transactionId: 't2' + } + ]; + + beforeEach(function () { + top.window.invibes = null; + document.cookie = ''; + this.cStub1 = sinon.stub(console, 'info'); + }); + + afterEach(function () { + this.cStub1.restore(); + }); + + describe('isBidRequestValid:', function () { + context('valid bid request:', function () { + it('returns true when bidder params.placementId is set', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + placementId: PLACEMENT_ID + } + } + + expect(spec.isBidRequestValid(validBid)).to.be.true; + }) + }); + + context('invalid bid request:', function () { + it('returns false when no params', function () { + const invalidBid = { + bidder: BIDDER_CODE + } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when placementId is not set', function() { + const invalidBid = { + bidder: BIDDER_CODE, + params: { + id: '5' + } + } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + }); + + describe('buildRequests', () => { + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends cookies with the bid request', () => { + const request = spec.buildRequests(bidRequests); + expect(request.options.withCredentials).to.equal(true); + }); + + it('has location, html id, placement and width/height', () => { + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const parsedData = request.data; + expect(parsedData.location).to.exist; + expect(parsedData.videoAdHtmlId).to.exist; + expect(parsedData.vId).to.exist; + expect(parsedData.width).to.exist; + expect(parsedData.height).to.exist; + }); + + it('sends all Placement Ids', () => { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[0].params.placementId); + expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[1].params.placementId); + }); + + it('uses cookies', () => { + global.document.cookie = 'ivNoCookie=1'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.be.undefined; + }); + + it('does not overwrite the domain id', () => { + top.window.invibes = window.invibes || {}; + top.window.invibes.dom = {}; + let request = spec.buildRequests(bidRequests); + }); + + it('doesnt send the domain id if not graduated', () => { + global.document.cookie = 'ivbsdid={"id":"p4vauj.4ekt9w","hc":3,"temp":1}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.not.exist; + }); + + it('graduate and send the domain id', () => { + global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s","hc":4,"temp":1}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.exist; + }); + + it('send the domain id if already graduated', () => { + global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s"}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.exist; + }); + }) + + describe('interpretResponse', function () { + let response = { + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '' + } + }; + + let expectedResponse = [{ + requestId: bidRequests[0].bidId, + cpm: 0.5, + width: 400, + height: 300, + creativeId: 123, + currency: 'EUR', + netRevenue: true, + ttl: 300, + ad: ` + + + + + ` + }]; + + context('when the response is not valid', function () { + it('handles response with no bids requested', () => { + let emptyResult = spec.interpretResponse({ body: response }); + expect(emptyResult).to.be.empty; + }); + + it('handles empty response', () => { + let emptyResult = spec.interpretResponse(null, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with bidding is not configured', () => { + let emptyResult = spec.interpretResponse({ body: { Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with no ads are received', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' }, AdReason: 'No ads' } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with no ads are received - no ad reason', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' } } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response when no placement Id matches', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '123456' }, Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response when placement Id is not present', () => { + let emptyResult = spec.interpretResponse({ BidModel: { }, Ads: [{ BidPrice: 1 }] }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + }); + + context('when the response is valid', function () { + it('responds with a valid bid', () => { + let result = spec.interpretResponse({ body: response }, { bidRequests }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('responds with a valid bid and uses logger', () => { + localStorage.InvibesDEBUG = true; + let result = spec.interpretResponse({ body: response }, { bidRequests }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('does not make multiple bids', () => { + localStorage.InvibesDEBUG = false; + let result = spec.interpretResponse({ body: response }, { bidRequests }); + let secondResult = spec.interpretResponse({ body: response }, { bidRequests }); + expect(secondResult).to.be.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + it('returns an iframe if enabled', () => { + let response = spec.getUserSyncs({iframeEnabled: true}); + expect(response.type).to.equal('iframe'); + expect(response.url).to.equal(SYNC_ENDPOINT); + }); + + it('returns undefined if iframe not enabled ', () => { + let response = spec.getUserSyncs({ iframeEnabled: false }); + expect(response).to.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/jcmBidAdapter_spec.js b/test/spec/modules/jcmBidAdapter_spec.js index 95356a9658e..27784def4f9 100644 --- a/test/spec/modules/jcmBidAdapter_spec.js +++ b/test/spec/modules/jcmBidAdapter_spec.js @@ -119,8 +119,8 @@ describe('jcmAdapter', () => { describe('getUserSyncs', () => { it('Verifies sync iframe option', () => { expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({ iframeEnabled: false})).to.be.undefined; - const options = spec.getUserSyncs({ iframeEnabled: true}); + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + const options = spec.getUserSyncs({ iframeEnabled: true }); expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('iframe'); @@ -128,8 +128,8 @@ describe('jcmAdapter', () => { }); it('Verifies sync image option', () => { - expect(spec.getUserSyncs({ image: false})).to.be.undefined; - const options = spec.getUserSyncs({ image: true}); + expect(spec.getUserSyncs({ image: false })).to.be.undefined; + const options = spec.getUserSyncs({ image: true }); expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('image'); diff --git a/test/spec/modules/peak226BidAdapter_spec.js b/test/spec/modules/peak226BidAdapter_spec.js index 37bbc1b67bd..f85e46c4289 100644 --- a/test/spec/modules/peak226BidAdapter_spec.js +++ b/test/spec/modules/peak226BidAdapter_spec.js @@ -27,22 +27,22 @@ describe('PeakAdapter', () => { }); }); - xdescribe('buildRequests', () => { - const bidRequests = [ - { - params: { - uid: '1234' - } - } - ]; - - it('sends bid request to URL via GET', () => { - const request = spec.buildRequests(bidRequests); - - expect(request.url).to.equal(`${URL}?uids=1234`); - expect(request.method).to.equal('GET'); - }); - }); + // xdescribe('buildRequests', () => { + // const bidRequests = [ + // { + // params: { + // uid: '1234' + // } + // } + // ]; + + // it('sends bid request to URL via GET', () => { + // const request = spec.buildRequests(bidRequests); + + // expect(request.url).to.equal(`${URL}?uids=1234`); + // expect(request.method).to.equal('GET'); + // }); + // }); describe('interpretResponse', () => { it('should handle empty response', () => { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 7214e841b54..328cc1fa750 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -675,7 +675,7 @@ describe('S2S Adapter', () => { adapter: 'prebidServer' }; - config.setConfig({ s2sConfig: options}); + config.setConfig({ s2sConfig: options }); sinon.assert.calledOnce(logErrorSpy); }); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index 7f7c71543f1..50ca4616a4b 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -19,6 +19,10 @@ const TIMEOUT = 2000; describe('Publisher Common ID', function () { describe('Decorate adUnits', function () { + before(function() { + window.document.cookie = COOKIE_NAME + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + }); + it('Check same cookie', function () { let adUnits1 = getAdUnits(); let adUnits2 = getAdUnits(); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 933b65c4574..b4793256ee0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -182,8 +182,8 @@ describe('PulsePoint Adapter Tests', () => { const nativeResponse = { 'native': { assets: [ - { title: { text: 'Ad Title'} }, - { data: { type: 1, value: 'Sponsored By: Brand' }}, + { title: { text: 'Ad Title' } }, + { data: { type: 1, value: 'Sponsored By: Brand' } }, { img: { type: 3, url: 'http://images.cdn.brand.com/123' } } ], link: { url: 'http://brand.clickme.com/' }, @@ -240,13 +240,13 @@ describe('PulsePoint Adapter Tests', () => { expect(spec.isBidRequestValid({ params: {} })).to.equal(false); expect(spec.isBidRequestValid({ params: { ct: 123 } })).to.equal(false); expect(spec.isBidRequestValid({ params: { cp: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { ct: 123, cp: 234 }})).to.equal(true); + expect(spec.isBidRequestValid({ params: { ct: 123, cp: 234 } })).to.equal(true); }); it('Verifies sync options', () => { expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({ iframeEnabled: false})).to.be.undefined; - const options = spec.getUserSyncs({ iframeEnabled: true}); + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + const options = spec.getUserSyncs({ iframeEnabled: true }); expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('iframe'); @@ -254,7 +254,7 @@ describe('PulsePoint Adapter Tests', () => { }); it('Verifies image pixel sync', () => { - const options = spec.getUserSyncs({ pixelEnabled: true}); + const options = spec.getUserSyncs({ pixelEnabled: true }); expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('image'); diff --git a/test/spec/modules/rockyouBidAdapter_spec.js b/test/spec/modules/rockyouBidAdapter_spec.js index 7c06c41485c..f929b50d581 100644 --- a/test/spec/modules/rockyouBidAdapter_spec.js +++ b/test/spec/modules/rockyouBidAdapter_spec.js @@ -205,7 +205,7 @@ describe('RockYouAdapter', () => { let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); localBidRequest.sizes = ['x', 'w']; - localBidRequest.mediaTypes = { banner: { sizes: ['y', 'z']} }; + localBidRequest.mediaTypes = { banner: { sizes: ['y', 'z'] } }; let results = spec.buildRequests([localBidRequest], { bidderRequestId: 'sample' diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6e27bdb87d8..1c4bccc69c5 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -512,6 +512,50 @@ describe('the rubicon adapter', () => { expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); }); + it('should send request with proper ad position', () => { + createVideoBidderRequest(); + var positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf'; + let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + let post = request.data; + let slot = post.slots[0]; + + expect(slot.position).to.equal('atf'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('btf'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'unknown'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = '123'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + + positionBidderRequest = clone(bidderRequest); + delete positionBidderRequest.bids[0].params.position; + expect(positionBidderRequest.bids[0].params.position).to.equal(undefined); + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + }); + it('should allow a floor price override', () => { createVideoBidderRequest(); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js new file mode 100644 index 00000000000..858e8bf37a0 --- /dev/null +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -0,0 +1,230 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/smartyadsBidAdapter'; + +describe('SmartyadsAdapter', () => { + let bid = { + bidId: '23fhj33i987f', + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } + }; + + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', () => { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//ssp-nj.webtradehub.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal('banner'); + }); + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', () => { + it('Should interpret banner response', () => { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', () => { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId'); + expect(dataItem.mediaType).to.not.exist; + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', () => { + const native = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'clickUrl', 'impressionTrackers', 'title', 'image', 'ttl', 'creativeId', 'netRevenue', 'currency'); + expect(dataItem.mediaType).to.not.exist; + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.clickUrl).to.equal('test.com'); + expect(dataItem.title).to.equal('Test'); + expect(dataItem.image).to.equal('test.com'); + expect(dataItem.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', () => { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', () => { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', () => { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', () => { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', () => { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', () => { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('//ssp-nj.webtradehub.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/somoaudienceBidAdapter_spec.js b/test/spec/modules/somoaudienceBidAdapter_spec.js index a6078a9c4dc..2189cacb0ec 100644 --- a/test/spec/modules/somoaudienceBidAdapter_spec.js +++ b/test/spec/modules/somoaudienceBidAdapter_spec.js @@ -51,7 +51,7 @@ describe('Somo Audience Adapter Tests', () => { expect(spec.isBidRequestValid({})).to.equal(false); expect(spec.isBidRequestValid(bidderBadSet[0])).to.equal(false); expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { placementId: '12345' }})).to.equal(true); + expect(spec.isBidRequestValid({ params: { placementId: '12345' } })).to.equal(true); }); it('Verifies buildRequests', () => { diff --git a/test/spec/modules/vubleBidAdapter_spec.js b/test/spec/modules/vubleBidAdapter_spec.js new file mode 100644 index 00000000000..3ecbd533c5f --- /dev/null +++ b/test/spec/modules/vubleBidAdapter_spec.js @@ -0,0 +1,268 @@ +// import or require modules necessary for the test, e.g.: + +import {expect} from 'chai'; +import {spec as adapter} from 'modules/vubleBidAdapter'; +import * as utils from 'src/utils'; + +describe('VubleAdapter', () => { + describe('Check methods existance', () => { + it('exists and is a function', () => { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + }); + + describe('Check method isBidRequestValid return', () => { + let bid = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.00 // optional + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; + + it('should be true', () => { + expect(adapter.isBidRequestValid(bid)).to.be.true; + }); + + it('should be false because the sizes are missing or in the wrong format', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.sizes = '640360'; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.sizes; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because the mediaType is missing or wrong', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.mediaTypes = {}; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.mediaTypes; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because the env is missing or wrong', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.params.env = 'us'; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.params.env; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because params.pubId is missing', () => { + let wrongBid = utils.deepClone(bid); + delete wrongBid.params.pubId; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because params.zoneId is missing', () => { + let wrongBid = utils.deepClone(bid); + delete wrongBid.params.zoneId; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + }); + + describe('Check buildRequests method', () => { + let sandbox; + before(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.vuble.tv/'); + }); + + // Bids to be formatted + let bid1 = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.50 // optional + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }; + let bid2 = { + bidder: 'vuble', + params: { + env: 'com', + pubId: '8', + zoneId: '2468', + referrer: 'http://www.vuble.fr/' + }, + sizes: '640x360', + mediaTypes: { + video: { + context: 'outstream' + } + }, + bidId: 'efgh' + }; + + // Formatted requets + let request1 = { + method: 'POST', + url: '//player.mediabong.net/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '3', + zone_id: '12345', + context: 'instream', + floor_price: 5.5, + url: 'http://www.vuble.tv/', + env: 'net', + bid_id: 'abdc' + } + }; + let request2 = { + method: 'POST', + url: '//player.mediabong.com/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '8', + zone_id: '2468', + context: 'outstream', + floor_price: 0, + url: 'http://www.vuble.fr/', + env: 'com', + bid_id: 'efgh' + } + }; + + it('must return the right formatted requests', () => { + let rs = adapter.buildRequests([bid1, bid2]); + expect(adapter.buildRequests([bid1, bid2])).to.deep.equal([request1, request2]); + }); + + after(() => { + sandbox.restore(); + }); + }); + + describe('Check interpretResponse method return', () => { + // Server's response + let response = { + body: { + status: 'ok', + cpm: 5.00, + creativeId: '2468', + url: 'https//player.mediabong.net/prebid/ad/a1b2c3d4' + } + }; + // bid Request + let bid = { + data: { + context: 'instream', + env: 'net', + width: '640', + height: '360', + pub_id: '3', + zone_id: '12345', + bid_id: 'abdc', + floor_price: 5.50 // optional + }, + method: 'POST', + url: '//player.mediabong.net/prebid/request' + }; + // Formatted reponse + let result = { + requestId: 'abdc', + cpm: 5.00, + width: '640', + height: '360', + ttl: 60, + creativeId: '2468', + netRevenue: true, + currency: 'USD', + vastUrl: 'https//player.mediabong.net/prebid/ad/a1b2c3d4' + }; + + it('should equal to the expected formatted result', () => { + expect(adapter.interpretResponse(response, bid)).to.deep.equal([result]); + }); + + it('should be empty because the status is missing or wrong', () => { + let wrongResponse = utils.deepClone(response); + wrongResponse.body.status = 'ko'; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + + wrongResponse = utils.deepClone(response); + delete wrongResponse.body.status; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + }); + + it('should be empty because the body is missing or wrong', () => { + let wrongResponse = utils.deepClone(response); + wrongResponse.body = [1, 2, 3]; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + + wrongResponse = utils.deepClone(response); + delete wrongResponse.body; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + }); + }); + + describe('Check getUserSyncs method return', () => { + // Sync options + let syncOptions = { + iframeEnabled: false + }; + // Server's response + let response = { + body: { + status: 'ok', + cpm: 5.00, + creativeId: '2468', + url: 'https//player.mediabong.net/prebid/ad/a1b2c3d4' + } + }; + // Formatted reponse + let result = { + type: 'iframe', + url: 'http://player.mediabong.net/csifr?1234' + }; + + it('should return an empty array', () => { + expect(adapter.getUserSyncs({}, [])).to.be.empty; + expect(adapter.getUserSyncs({}, [])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + syncOptions.iframeEnabled = true; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + }); + + it('should be equal to the expected result', () => { + response.body.iframeSync = 'http://player.mediabong.net/csifr?1234'; + expect(adapter.getUserSyncs(syncOptions, [response])).to.deep.equal([result]); + }) + }); +}); diff --git a/test/spec/modules/yieldbotBidAdapter_spec.js b/test/spec/modules/yieldbotBidAdapter_spec.js new file mode 100644 index 00000000000..206645acd95 --- /dev/null +++ b/test/spec/modules/yieldbotBidAdapter_spec.js @@ -0,0 +1,1326 @@ +import { expect } from 'chai'; +import find from 'core-js/library/fn/array/find'; +import { newBidder } from 'src/adapters/bidderFactory'; +import AdapterManager from 'src/adaptermanager'; +import { newAuctionManager } from 'src/auctionManager'; +import * as utils from 'src/utils'; +import * as urlUtils from 'src/url'; +import events from 'src/events'; +import { YieldbotAdapter, spec } from 'modules/yieldbotBidAdapter'; + +before(function() { + YieldbotAdapter.clearAllCookies(); +}); +describe('Yieldbot Adapter Unit Tests', function() { + const ALL_SEARCH_PARAMS = ['apie', 'bt', 'cb', 'cts_ad', 'cts_imp', 'cts_ini', 'cts_js', 'cts_ns', 'cts_rend', 'cts_res', 'e', 'ioa', 'it', 'la', 'lo', 'lpv', 'lpvi', 'mtp', 'np', 'pvd', 'pvi', 'r', 'ri', 'sb', 'sd', 'si', 'slot', 'sn', 'ssz', 'to', 'ua', 'v', 'vi']; + + const BID_LEADERBOARD_728x90 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + }, + adUnitCode: '/0000000/leaderboard', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c19', + sizes: [728, 90], + bidId: '2240b2af6064bb', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_MEDREC_300x600 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + }, + adUnitCode: '/0000000/side-bar', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c20', + sizes: [300, 600], + bidId: '332067957eaa33', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_MEDREC_300x250 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + }, + adUnitCode: '/0000000/medrec', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + sizes: [[300, 250]], + bidId: '49d7fe5c3a15ed', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_SKY160x600 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' + }, + adUnitCode: '/0000000/side-bar', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + sizes: [160, 600], + bidId: '49d7fe5c3a16ee', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const AD_UNITS = [ + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c19', + code: '/00000000/leaderboard', + sizes: [728, 90], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + } + } + ] + }, + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c20', + code: '/00000000/medrec', + sizes: [[300, 250]], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] + }, + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + code: '/00000000/multi-size', + sizes: [[300, 600]], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] + }, + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c22', + code: '/00000000/skyscraper', + sizes: [[160, 600]], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' + } + } + ] + } + ]; + + const INTERPRET_RESPONSE_BID_REQUEST = { + method: 'GET', + url: '//i.yldbt.com/m/1234/v1/init', + data: { + cts_js: 1518184900582, + cts_ns: 1518184900582, + v: 'pbjs-yb-1.0.0', + vi: 'jdg00eijgpvemqlz73', + si: 'jdg00eil9y4mcdo850', + pvd: 6, + pvi: 'jdg03ai5kp9k1rkheh', + lpv: 1518184868108, + lpvi: 'jdg02lfwmdx8n0ncgc', + bt: 'init', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36', + np: 'MacIntel', + la: 'en-US', + to: 5, + sd: '2560x1440', + lo: 'http://localhost:9999/test/spec/e2e/gpt-examples/gpt_yieldbot.html', + r: '', + e: '', + sn: 'leaderboard|medrec|medrec|skyscraper', + ssz: '728x90|300x250|300x600|160x600', + cts_ini: 1518184900591 + }, + yieldbotSlotParams: { + psn: '1234', + sn: 'leaderboard|medrec|medrec|skyscraper', + ssz: '728x90|300x250|300x600|160x600', + bidIdMap: { + 'jdg03ai5kp9k1rkheh:leaderboard:728x90': '2240b2af6064bb', + 'jdg03ai5kp9k1rkheh:medrec:300x250': '49d7fe5c3a15ed', + 'jdg03ai5kp9k1rkheh:medrec:300x600': '332067957eaa33', + 'jdg03ai5kp9k1rkheh:skyscraper:160x600': '49d7fe5c3a16ee' + } + }, + options: { + withCredentials: true, + customHeaders: { + Accept: 'application/json' + } + } + }; + + const INTERPRET_RESPONSE_SERVER_RESPONSE = { + body: { + pvi: 'jdg03ai5kp9k1rkheh', + subdomain_iframe: 'ads-adseast-vpc', + url_prefix: 'http://ads-adseast-vpc.yldbt.com/m/', + slots: [ + { + slot: 'leaderboard', + cpm: '800', + size: '728x90' + }, + { + slot: 'medrec', + cpm: '300', + size: '300x250' + }, + { + slot: 'medrec', + cpm: '800', + size: '300x600' + }, + { + slot: 'skyscraper', + cpm: '300', + size: '160x600' + } + ], + user_syncs: [ + 'https://usersync.dd9693a32aa1.com/00000000.gif?p=a', + 'https://usersync.3b19503b37d8.com/00000000.gif?p=b', + 'https://usersync.5cb389d36d30.com/00000000.gif?p=c' + ] + }, + headers: {} + }; + + let FIXTURE_AD_UNITS, FIXTURE_SERVER_RESPONSE, FIXTURE_BID_REQUEST, FIXTURE_BID_REQUESTS, FIXTURE_BIDS; + beforeEach(function() { + FIXTURE_AD_UNITS = utils.deepClone(AD_UNITS); + FIXTURE_BIDS = { + BID_LEADERBOARD_728x90: utils.deepClone(BID_LEADERBOARD_728x90), + BID_MEDREC_300x600: utils.deepClone(BID_MEDREC_300x600), + BID_MEDREC_300x250: utils.deepClone(BID_MEDREC_300x250), + BID_SKY160x600: utils.deepClone(BID_SKY160x600) + }; + + FIXTURE_BID_REQUEST = utils.deepClone(INTERPRET_RESPONSE_BID_REQUEST); + FIXTURE_SERVER_RESPONSE = utils.deepClone(INTERPRET_RESPONSE_SERVER_RESPONSE); + FIXTURE_BID_REQUESTS = [ + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_MEDREC_300x250, + FIXTURE_BIDS.BID_SKY160x600 + ]; + }); + + afterEach(function() { + YieldbotAdapter._optOut = false; + YieldbotAdapter.clearAllCookies(); + YieldbotAdapter._isInitialized = false; + YieldbotAdapter.initialize(); + }); + + describe('Adapter exposes BidderSpec API', function() { + it('code', function() { + expect(spec.code).to.equal('yieldbot'); + }); + it('supportedMediaTypes', function() { + expect(spec.supportedMediaTypes).to.deep.equal(['banner']); + }); + it('isBidRequestValid', function() { + expect(spec.isBidRequestValid).to.be.a('function'); + }); + it('buildRequests', function() { + expect(spec.buildRequests).to.be.a('function'); + }); + it('interpretResponse', function() { + expect(spec.interpretResponse).to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + }; + + it('valid parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(true); + }); + + it('undefined parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + }); + + it('falsey string parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: '', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: '' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 0 + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + }); + + it('parameters type invalid', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 0 + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: { name: 'foo' }, + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + }); + + it('invalid sizes type', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: {} + })).to.equal(true); + }); + }); + + describe('getSlotRequestParams', function() { + const EMPTY_SLOT_PARAMS = { sn: '', ssz: '', bidIdMap: {} }; + + it('should default to empty slot params', function() { + expect(YieldbotAdapter.getSlotRequestParams('')).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams()).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams('', [])).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams(0, [])).to.deep.equal(EMPTY_SLOT_PARAMS); + }); + + it('should build slot bid request parameters', function() { + const bidRequests = [ + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_MEDREC_300x250 + ]; + const slotParams = YieldbotAdapter.getSlotRequestParams('f0e1d2c', bidRequests); + + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('leaderboard|medrec'); + expect(slotParams.ssz).to.equal('728x90|300x600.300x250'); + + let bidId = slotParams.bidIdMap['f0e1d2c:leaderboard:728x90']; + expect(bidId).to.equal('2240b2af6064bb'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x250']; + expect(bidId).to.equal('49d7fe5c3a15ed'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x600']; + expect(bidId).to.equal('332067957eaa33'); + }); + + it('should build slot bid request parameters in order of bidRequests', function() { + const bidRequests = [ + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x250 + ]; + + const slotParams = YieldbotAdapter.getSlotRequestParams('f0e1d2c', bidRequests); + + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('medrec|leaderboard'); + expect(slotParams.ssz).to.equal('300x600.300x250|728x90'); + + let bidId = slotParams.bidIdMap['f0e1d2c:leaderboard:728x90']; + expect(bidId).to.equal('2240b2af6064bb'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x250']; + expect(bidId).to.equal('49d7fe5c3a15ed'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x600']; + expect(bidId).to.equal('332067957eaa33'); + }); + + it('should exclude slot bid requests with malformed sizes', function() { + const bid = FIXTURE_BIDS.BID_MEDREC_300x250; + bid.sizes = ['300x250']; + const bidRequests = [bid, FIXTURE_BIDS.BID_LEADERBOARD_728x90]; + const slotParams = YieldbotAdapter.getSlotRequestParams('affffffe', bidRequests); + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('leaderboard'); + expect(slotParams.ssz).to.equal('728x90'); + }); + }); + + describe('getCookie', function() { + it('should return null if cookie name not found', function() { + const cookieName = YieldbotAdapter.newId(); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + }); + + describe('setCookie', function() { + it('should set a root path first-party cookie with temporal expiry', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, 2000, '/'); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(cookieValue); + + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should set a root path first-party cookie with session expiry', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, null, '/'); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(cookieValue); + + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should fail to set a cookie x-domain', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, null, '/', `${cookieName}.com`); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + }); + + describe('clearAllcookies', function() { + it('should delete all first-party cookies', function() { + let idx, cookieLabels = YieldbotAdapter._cookieLabels, cookieName, cookieValue; + for (idx = 0; idx < cookieLabels.length; idx++) { + YieldbotAdapter.deleteCookie('__ybot' + cookieLabels[idx]); + } + + YieldbotAdapter.sessionBlocked = true; + expect(YieldbotAdapter.sessionBlocked, 'sessionBlocked').to.equal(true); + + const userId = YieldbotAdapter.userId; + expect(YieldbotAdapter.userId, 'userId').to.equal(userId); + + const sessionId = YieldbotAdapter.sessionId; + expect(YieldbotAdapter.sessionId, 'sessionId').to.equal(sessionId); + + const pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth, 'returned pageviewDepth').to.equal(1); + expect(YieldbotAdapter.pageviewDepth, 'get pageviewDepth').to.equal(2); + + const lastPageviewId = YieldbotAdapter.newId(); + YieldbotAdapter.lastPageviewId = lastPageviewId; + expect(YieldbotAdapter.lastPageviewId, 'get lastPageviewId').to.equal(lastPageviewId); + + const lastPageviewTime = Date.now(); + YieldbotAdapter.lastPageviewTime = lastPageviewTime; + expect(YieldbotAdapter.lastPageviewTime, 'lastPageviewTime').to.equal(lastPageviewTime); + + const urlPrefix = YieldbotAdapter.urlPrefix('http://here.there.com/ad/'); + expect(YieldbotAdapter.urlPrefix(), 'urlPrefix').to.equal('http://here.there.com/ad/'); + + for (idx = 0; idx < cookieLabels.length; idx++) { + cookieValue = YieldbotAdapter.getCookie('__ybot' + cookieLabels[idx]); + expect(!!cookieValue, 'setter: ' + cookieLabels[idx]).to.equal(true); + } + + YieldbotAdapter.clearAllCookies(); + + for (idx = 0; idx < cookieLabels.length; idx++) { + cookieName = '__ybot' + cookieLabels[idx]; + cookieValue = YieldbotAdapter.getCookie(cookieName); + expect(cookieValue, cookieName).to.equal(null); + }; + }); + }); + + describe('sessionBlocked', function() { + const cookieName = '__ybotn'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should return true if cookie value is interpreted as non-zero', function() { + YieldbotAdapter.setCookie(cookieName, '1', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "1"').to.equal(true); + + YieldbotAdapter.setCookie(cookieName, '10.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "10.01"').to.equal(true); + + YieldbotAdapter.setCookie(cookieName, '-10.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "-10.01"').to.equal(true); + + YieldbotAdapter.setCookie(cookieName, 1, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the number 1').to.equal(true); + }); + + it('should return false if cookie name not found', function() { + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); + + it('should return false if cookie value is interpreted as zero', function() { + YieldbotAdapter.setCookie(cookieName, '0', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "0"').to.equal(false); + + YieldbotAdapter.setCookie(cookieName, '.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string ".01"').to.equal(false); + + YieldbotAdapter.setCookie(cookieName, '-.9', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "-.9"').to.equal(false); + + YieldbotAdapter.setCookie(cookieName, 0, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the number 0').to.equal(false); + }); + + it('should return false if cookie value source is a non-numeric string', function() { + YieldbotAdapter.setCookie(cookieName, 'true', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); + + it('should return false if cookie value source is a boolean', function() { + YieldbotAdapter.setCookie(cookieName, true, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); + + it('should set sessionBlocked', function() { + YieldbotAdapter.sessionBlocked = true; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = false; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + + YieldbotAdapter.sessionBlocked = 1; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = 0; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + + YieldbotAdapter.sessionBlocked = '1'; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = ''; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); + }); + + describe('userId', function() { + const cookieName = '__ybotu'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should set a user Id if cookie does not exist', function() { + const userId = YieldbotAdapter.userId; + expect(userId).to.match(/[0-9a-z]{18}/); + }); + + it('should return user Id if cookie exists', function() { + const expectedUserId = YieldbotAdapter.newId(); + YieldbotAdapter.setCookie(cookieName, expectedUserId, 2000, '/'); + const userId = YieldbotAdapter.userId; + expect(userId).to.equal(expectedUserId); + }); + }); + + describe('sessionId', function() { + const cookieName = '__ybotsi'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should set a session Id if cookie does not exist', function() { + const sessionId = YieldbotAdapter.sessionId; + expect(sessionId).to.match(/[0-9a-z]{18}/); + }); + + it('should return session Id if cookie exists', function() { + const expectedSessionId = YieldbotAdapter.newId(); + YieldbotAdapter.setCookie(cookieName, expectedSessionId, 2000, '/'); + const sessionId = YieldbotAdapter.sessionId; + expect(sessionId).to.equal(expectedSessionId); + }); + }); + + describe('lastPageviewId', function() { + const cookieName = '__ybotlpvi'; + + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should return empty string if cookie does not exist', function() { + const lastBidId = YieldbotAdapter.lastPageviewId; + expect(lastBidId).to.equal(''); + }); + + it('should set an id string', function() { + const id = YieldbotAdapter.newId(); + YieldbotAdapter.lastPageviewId = id; + const lastBidId = YieldbotAdapter.lastPageviewId; + expect(lastBidId).to.equal(id); + }); + }); + + describe('lastPageviewTime', function() { + const cookieName = '__ybotlpv'; + + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should return zero if cookie does not exist', function() { + const lastBidTime = YieldbotAdapter.lastPageviewTime; + expect(lastBidTime).to.equal(0); + }); + + it('should set a timestamp', function() { + const ts = Date.now(); + YieldbotAdapter.lastPageviewTime = ts; + const lastBidTime = YieldbotAdapter.lastPageviewTime; + expect(lastBidTime).to.equal(ts); + }); + }); + + describe('pageviewDepth', function() { + const cookieName = '__ybotpvd'; + + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should return one (1) if cookie does not exist', function() { + const pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(1); + }); + + it('should increment the integer string for depth', function() { + let pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(1); + + pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(2); + }); + }); + + describe('urlPrefix', function() { + const cookieName = '__ybotc'; + const protocol = document.location.protocol; + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); + + it('should set the default prefix if cookie does not exist', function(done) { + const urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix).to.equal('//i.yldbt.com/m/'); + done(); + }); + + it('should return prefix if cookie exists', function() { + YieldbotAdapter.setCookie(cookieName, protocol + '//closest.az.com/path/', 2000, '/'); + const urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix).to.equal(protocol + '//closest.az.com/path/'); + }); + + it('should reset prefix if default already set', function() { + const defaultUrlPrefix = YieldbotAdapter.urlPrefix(); + const url = protocol + '//close.az.com/path/'; + expect(defaultUrlPrefix).to.equal('//i.yldbt.com/m/'); + + let urlPrefix = YieldbotAdapter.urlPrefix(url); + expect(urlPrefix, 'reset prefix').to.equal(url); + + urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix, 'subsequent request').to.equal(url); + }); + + it('should set containing document protocol', function() { + let urlPrefix = YieldbotAdapter.urlPrefix('http://close.az.com/path/'); + expect(urlPrefix, 'http - use: ' + protocol).to.equal(protocol + '//close.az.com/path/'); + + urlPrefix = YieldbotAdapter.urlPrefix('https://close.az.com/path/'); + expect(urlPrefix, 'https - use: ' + protocol).to.equal(protocol + '//close.az.com/path/'); + }); + + it('should fallback to default for invalid argument', function() { + let urlPrefix = YieldbotAdapter.urlPrefix('//close.az.com/path/'); + expect(urlPrefix, 'Url w/o protocol').to.equal('//i.yldbt.com/m/'); + + urlPrefix = YieldbotAdapter.urlPrefix('mumble'); + expect(urlPrefix, 'Invalid Url').to.equal('//i.yldbt.com/m/'); + }); + }); + + describe('initBidRequestParams', function() { + it('should build common bid request state parameters', function() { + const params = YieldbotAdapter.initBidRequestParams( + [ + { + 'params': { + psn: '1234', + slot: 'medrec' + }, + sizes: [[300, 250], [300, 600]] + } + ] + ); + + const expectedParamKeys = [ + 'v', + 'vi', + 'si', + 'pvi', + 'pvd', + 'lpvi', + 'bt', + 'lo', + 'r', + 'sd', + 'to', + 'la', + 'np', + 'ua', + 'lpv', + 'cts_ns', + 'cts_js', + 'e' + ]; + + const missingKeys = []; + expectedParamKeys.forEach((item) => { + if (item in params === false) { + missingKeys.push(item); + } + }); + const extraKeys = []; + Object.keys(params).forEach((item) => { + if (!find(expectedParamKeys, param => param === item)) { + extraKeys.push(item); + } + }); + + expect( + missingKeys.length, + `\nExpected: ${expectedParamKeys}\nMissing keys: ${JSON.stringify(missingKeys)}`) + .to.equal(0); + expect( + extraKeys.length, + `\nExpected: ${expectedParamKeys}\nExtra keys: ${JSON.stringify(extraKeys)}`) + .to.equal(0); + }); + }); + + describe('buildRequests', function() { + it('should not return bid requests if optOut', function() { + YieldbotAdapter._optOut = true; + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + }); + + it('should not return bid requests if sessionBlocked', function() { + YieldbotAdapter.sessionBlocked = true; + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + YieldbotAdapter.sessionBlocked = false; + }); + + it('should re-enable requests when sessionBlocked expires', function() { + const cookieName = '__ybotn'; + YieldbotAdapter.setCookie( + cookieName, + 1, + 2000, + '/'); + let requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + YieldbotAdapter.deleteCookie(cookieName); + requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(1); + }); + + it('should return a single BidRequest object', function() { + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(1); + }); + + it('should have expected server options', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const expectedOptions = { + withCredentials: true, + customHeaders: { + Accept: 'application/json' + } + }; + expect(request.options).to.eql(expectedOptions); + }); + + it('should be a GET request', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.method).to.equal('GET'); + }); + + it('should have bid request specific params', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.data).to.not.equal(undefined); + + const expectedParamKeys = [ + 'v', + 'vi', + 'si', + 'pvi', + 'pvd', + 'lpvi', + 'bt', + 'lo', + 'r', + 'sd', + 'to', + 'la', + 'np', + 'ua', + 'sn', + 'ssz', + 'lpv', + 'cts_ns', + 'cts_js', + 'cts_ini', + 'e' + ]; + + const missingKeys = []; + expectedParamKeys.forEach((item) => { + if (item in request.data === false) { + missingKeys.push(item); + } + }); + const extraKeys = []; + Object.keys(request.data).forEach((item) => { + if (!find(expectedParamKeys, param => param === item)) { + extraKeys.push(item); + } + }); + + expect( + missingKeys.length, + `\nExpected: ${expectedParamKeys}\nMissing keys: ${JSON.stringify(missingKeys)}`) + .to.equal(0); + expect( + extraKeys.length, + `\nExpected: ${expectedParamKeys}\nExtra keys: ${JSON.stringify(extraKeys)}`) + .to.equal(0); + }); + + it('should have the correct bidUrl form', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const bidUrl = '//i.yldbt.com/m/1234/v1/init'; + expect(request.url).to.equal(bidUrl); + }); + + it('should set the bid request slot/bidId mapping', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams).to.not.equal(undefined); + expect(request.yieldbotSlotParams.bidIdMap).to.not.equal(undefined); + + const map = {}; + map[request.data.pvi + ':leaderboard:728x90'] = '2240b2af6064bb'; + map[request.data.pvi + ':medrec:300x250'] = '49d7fe5c3a15ed'; + map[request.data.pvi + ':medrec:300x600'] = '332067957eaa33'; + map[request.data.pvi + ':skyscraper:160x600'] = '49d7fe5c3a16ee'; + expect(request.yieldbotSlotParams.bidIdMap).to.eql(map); + }); + + it('should set the bid request publisher number', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.psn).to.equal('1234'); + }); + + it('should have unique slot name parameter', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.sn).to.equal('leaderboard|medrec|skyscraper'); + }); + + it('should have slot sizes parameter', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.ssz).to.equal('728x90|300x600.300x250|160x600'); + }); + + it('should use edge server Url prefix if set', function() { + const cookieName = '__ybotc'; + YieldbotAdapter.setCookie( + cookieName, + 'http://close.edge.adserver.com/', + 2000, + '/'); + + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.url).to.match(/^http:\/\/close\.edge\.adserver\.com\//); + }); + + it('should be adapter loaded before navigation start time', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const timeDiff = request.data.cts_ns - request.data.cts_js; + expect(timeDiff >= 0, 'adapter loaded < nav').to.equal(true); + }); + + it('should be navigation start before bid request time', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const timeDiff = request.data.cts_ini - request.data.cts_ns; + expect(timeDiff >= 0, 'nav start < request').to.equal(true); + }); + }); + + describe('interpretResponse', function() { + it('should not return Bids if optOut', function() { + YieldbotAdapter._optOut = true; + const responses = YieldbotAdapter.interpretResponse(); + expect(responses.length).to.equal(0); + }); + + it('should not return Bids if no server response slot bids', function() { + FIXTURE_SERVER_RESPONSE.body.slots = []; + const responses = YieldbotAdapter.interpretResponse(FIXTURE_SERVER_RESPONSE, FIXTURE_BID_REQUEST); + expect(responses.length).to.equal(0); + }); + + it('should not include Bid if missing cpm', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[1].cpm; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); + + it('should not include Bid if missing size', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[2].size; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); + + it('should not include Bid if missing slot', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[3].slot; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); + + it('should have a valid creativeId', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(4); + responses.forEach((bid) => { + expect(bid.creativeId).to.match(/[0-9a-z]{18}/); + const containerDivId = 'ybot-' + bid.creativeId; + const re = new RegExp(containerDivId); + expect(re.test(bid.ad)).to.equal(true); + }); + }); + + it('should specify Net revenue type for bid', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses[0].netRevenue).to.equal(true); + }); + + it('should specify USD currency for bid', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses[1].currency).to.equal('USD'); + }); + + it('should set edge server Url prefix', function() { + FIXTURE_SERVER_RESPONSE.body.url_prefix = 'http://close.edge.adserver.com/'; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + const edgeServerUrlPrefix = YieldbotAdapter.getCookie('__ybotc'); + + const protocol = document.location.protocol; + const beginsRegex = new RegExp('^' + protocol + '\/\/close\.edge\.adserver\.com\/'); + const containsRegex = new RegExp(protocol + '\/\/close\.edge\.adserver\.com\/'); + expect(edgeServerUrlPrefix).to.match(beginsRegex); + expect(responses[0].ad).to.match(containsRegex); + }); + }); + + describe('getUserSyncs', function() { + let responses; + beforeEach(function () { + responses = [FIXTURE_SERVER_RESPONSE]; + }); + it('should return empty Array when server response property missing', function() { + delete responses[0].body.user_syncs; + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs.length).to.equal(0); + }); + + it('should return empty Array when server response property is empty', function() { + responses[0].body.user_syncs = []; + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs.length).to.equal(0); + }); + + it('should return empty Array pixel disabled', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: false }, responses); + expect(userSyncs.length).to.equal(0); + }); + + it('should return empty Array pixel option not provided', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelNotHere: true }, responses); + expect(userSyncs.length).to.equal(0); + }); + + it('should return image type pixels', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs).to.eql( + [ + { type: 'image', url: 'https://usersync.dd9693a32aa1.com/00000000.gif?p=a' }, + { type: 'image', url: 'https://usersync.3b19503b37d8.com/00000000.gif?p=b' }, + { type: 'image', url: 'https://usersync.5cb389d36d30.com/00000000.gif?p=c' } + ] + ); + }); + }); + + describe('Adapter Auction Behavior', function() { + AdapterManager.bidderRegistry['yieldbot'] = newBidder(spec); + let sandbox, server, auctionManager; + const bidUrlRegexp = /yldbt\.com\/m\/1234\/v1\/init/; + beforeEach(function() { + sandbox = sinon.sandbox.create({ useFakeServer: true }); + server = sandbox.server; + server.respondImmediately = true; + server.respondWith( + 'GET', + bidUrlRegexp, + [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(FIXTURE_SERVER_RESPONSE.body) + ] + ); + FIXTURE_SERVER_RESPONSE.user_syncs = []; + auctionManager = newAuctionManager(); + }); + + afterEach(function() { + auctionManager = null; + sandbox.restore(); + YieldbotAdapter._bidRequestCount = 0; + }); + + it('should provide auction bids', function(done) { + let bidCount = 0; + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + const bidResponseHandler = (event) => { + bidCount++; + if (bidCount === 4) { + events.off('bidResponse', bidResponseHandler); + done(); + } + }; + events.on('bidResponse', bidResponseHandler); + firstAuction.callBids(); + }); + + it('should provide multiple auctions with correct bid cpms', function(done) { + let bidCount = 0; + let firstAuctionId = ''; + let secondAuctionId = ''; + /* + * 'bidResponse' event handler checks for respective adUnit auctions and bids + */ + const bidResponseHandler = (event) => { + try { + switch (true) { + case event.adUnitCode === '/00000000/leaderboard' && event.auctionId === firstAuctionId: + expect(event.cpm, 'leaderboard, first auction cpm').to.equal(8); + break; + case event.adUnitCode === '/00000000/medrec' && event.auctionId === firstAuctionId: + expect(event.cpm, 'medrec, first auction cpm').to.equal(3); + break; + case event.adUnitCode === '/00000000/multi-size' && event.auctionId === firstAuctionId: + expect(event.cpm, 'multi-size, first auction cpm').to.equal(8); + break; + case event.adUnitCode === '/00000000/skyscraper' && event.auctionId === firstAuctionId: + expect(event.cpm, 'skyscraper, first auction cpm').to.equal(3); + break; + case event.adUnitCode === '/00000000/medrec' && event.auctionId === secondAuctionId: + expect(event.cpm, 'medrec, second auction cpm').to.equal(1.11); + break; + case event.adUnitCode === '/00000000/multi-size' && event.auctionId === secondAuctionId: + expect(event.cpm, 'multi-size, second auction cpm').to.equal(2.22); + break; + case event.adUnitCode === '/00000000/skyscraper' && event.auctionId === secondAuctionId: + expect(event.cpm, 'skyscraper, second auction cpm').to.equal(3.33); + break; + default: + done(new Error(`Bid response to assert not found: ${event.adUnitCode}:${event.size}:${event.auctionId}, [${firstAuctionId}, ${secondAuctionId}]`)); + } + bidCount++; + if (bidCount === 7) { + events.off('bidResponse', bidResponseHandler); + done(); + } + } catch (err) { + done(err); + } + }; + events.on('bidResponse', bidResponseHandler); + + /* + * First auction + */ + const firstAdUnits = FIXTURE_AD_UNITS; + const firstAdUnitCodes = FIXTURE_AD_UNITS.map(unit => unit.code); + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuctionId = firstAuction.getAuctionId(); + firstAuction.callBids(); + + /* + * Second auction with different bid values and fewer slots + */ + FIXTURE_AD_UNITS.shift(); + const FIXTURE_SERVER_RESPONSE_2 = utils.deepClone(FIXTURE_SERVER_RESPONSE); + FIXTURE_SERVER_RESPONSE_2.user_syncs = []; + FIXTURE_SERVER_RESPONSE_2.body.slots.shift(); + FIXTURE_SERVER_RESPONSE_2.body.slots.forEach((bid, idx) => { const num = idx + 1; bid.cpm = `${num}${num}${num}`; }); + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuctionId = secondAuction.getAuctionId(); + server.respondWith( + 'GET', + bidUrlRegexp, + [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(FIXTURE_SERVER_RESPONSE_2.body) + ] + ); + secondAuction.callBids(); + }); + + it('should have refresh bid type after the first auction', function(done) { + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuction.callBids(); + + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuction.callBids(); + + const firstRequest = urlUtils.parse(server.firstRequest.url); + expect(firstRequest.search.bt, 'First request bid type').to.equal('init'); + + const secondRequest = urlUtils.parse(server.secondRequest.url); + expect(secondRequest.search.bt, 'Second request bid type').to.equal('refresh'); + + done(); + }); + + it('should use server response url_prefix property value after the first auction', function(done) { + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuction.callBids(); + + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuction.callBids(); + + expect(server.firstRequest.url, 'Default url prefix').to.match(/i\.yldbt\.com\/m\//); + expect(server.secondRequest.url, 'Locality url prefix').to.match(/ads-adseast-vpc\.yldbt\.com\/m\//); + + done(); + }); + + it('should increment the session page view depth only before the first auction', function(done) { + /* + * First visit: two bid requests + */ + for (let idx = 0; idx < 2; idx++) { + auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ).callBids(); + } + + const firstRequest = urlUtils.parse(server.firstRequest.url); + expect(firstRequest.search.pvd, 'First pvd').to.equal('1'); + + const secondRequest = urlUtils.parse(server.secondRequest.url); + expect(secondRequest.search.pvd, 'Second pvd').to.equal('1'); + + /* + * Next visit: two bid requests + */ + YieldbotAdapter._isInitialized = false; + YieldbotAdapter.initialize(); + for (let idx = 0; idx < 2; idx++) { + auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ).callBids(); + } + + const nextVisitFirstRequest = urlUtils.parse(server.thirdRequest.url); + expect(nextVisitFirstRequest.search.pvd, 'Second visit, first pvd').to.equal('2'); + + const nextVisitSecondRequest = urlUtils.parse(server.lastRequest.url); + expect(nextVisitSecondRequest.search.pvd, 'Second visit, second pvd').to.equal('2'); + + done(); + }); + }); +}); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index bc9e86c0e0e..ca2a9afc103 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -155,7 +155,7 @@ describe('bidders created by newBidder', () => { const bidder = newBidder(spec); const url = 'test.url.com'; const data = { arg: 2 }; - const options = { contentType: 'application/json'}; + const options = { contentType: 'application/json' }; spec.isBidRequestValid.returns(true); spec.buildRequests.returns({ method: 'POST', diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index a515a7aa010..f772802f21f 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -365,7 +365,7 @@ describe('Unit: Prebid Module', function () { const customConfigObject = { 'buckets': [ { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 }, - { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05}, + { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05 }, { 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 }, { 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 } ]