From 4954ff09504fa28df0cdf8cee46925dd790b833c Mon Sep 17 00:00:00 2001 From: pyang Date: Thu, 5 Oct 2017 09:40:54 -0700 Subject: [PATCH 01/14] Conversant adapter initial support for prebid 1.0 --- modules/conversantBidAdapter.js | 386 ++++++++++++-------------------- 1 file changed, 140 insertions(+), 246 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index d51008559f2..5ab91614c57 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,278 +1,172 @@ 'use strict'; -var VERSION = '2.1.0'; -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var ajax = require('src/ajax').ajax; -var adaptermanager = require('src/adaptermanager'); - -/** - * Adapter for requesting bids from Conversant - */ -var ConversantAdapter = function () { - var w = window; - var n = navigator; - - // production endpoint - var conversantUrl = '//media.msg.dotomi.com/s2s/header/24?callback=$$PREBID_GLOBAL$$.conversantResponse'; - - // SSAPI returns JSONP with window.pbjs.conversantResponse as the cb - var appendScript = function (code) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.className = 'cnvr-response'; - - try { - script.appendChild(document.createTextNode(code)); - document.getElementsByTagName('head')[0].appendChild(script); - } catch (e) { - script.text = code; - document.getElementsByTagName('head')[0].appendChild(script); - } - }; - - var getDNT = function () { - return n.doNotTrack === '1' || w.doNotTrack === '1' || n.msDoNotTrack === '1' || n.doNotTrack === 'yes'; - }; - - var getDevice = function () { - const language = n.language ? 'language' : 'userLanguage'; - return { - h: screen.height, - w: screen.width, - dnt: getDNT() ? 1 : 0, - language: n[language].split('-')[0], - make: n.vendor ? n.vendor : '', - ua: n.userAgent - }; - }; - - var callBids = function (params) { - var conversantBids = params.bids || []; - requestBids(conversantBids); - }; - - var requestBids = function (bidReqs) { - // build bid request object - var page = location.pathname + location.search + location.hash; - var siteId = ''; - var conversantImps = []; - var conversantBidReqs; - var secure = 0; - - // build impression array for conversant - utils._each(bidReqs, function (bid) { - var bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - var adW = 0; - var adH = 0; - var format; - var tagId; - var pos; - var imp; - +import * as utils from 'src/utils'; +// import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'conversant'; +const URL = '//cmedia102.dev2.vcmedia.com/s2s/header/24'; +const VERSION = '2.2.0'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['cnvr'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} + * bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.site_id); + }, + /** + * 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) { + // console.log('hello world ', JSON.stringify(validBidRequests)); + const loc = utils.getTopWindowLocation(); + let siteId = ''; + let secure = 0; + let page = loc.pathname + loc.search + loc.hash; + let requestId = ''; + + const conversantImps = validBidRequests.map(function(bid) { + const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const tagId = utils.getBidIdParameter('tag_id', bid.params); + const pos = utils.getBidIdParameter('position', bid.params); + + siteId = utils.getBidIdParameter('site_id', bid.params); secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; - siteId = utils.getBidIdParameter('site_id', bid.params) + ''; - tagId = utils.getBidIdParameter('tag_id', bid.params); - pos = utils.getBidIdParameter('position', bid.params); - - // Allow sizes to be overridden per placement - var bidSizes = Array.isArray(bid.params.sizes) ? bid.params.sizes : bid.sizes; + requestId = bid.requestId; - if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - adW = bidSizes[0]; - adH = bidSizes[1]; - } else { - format = []; - utils._each(bidSizes, function (bidSize) { - format.push({ - w: bidSize[0], - h: bidSize[1] - }); - }); - } + const format = bid.sizes.map(function(d) { + return {w: d[0], h: d[1]}; + }); - imp = { + const imp = { id: bid.bidId, secure: secure, bidfloor: bidfloor || 0, displaymanager: 'Prebid.js', - displaymanagerver: VERSION + displaymanaerver: VERSION }; - if (tagId !== '') { + if (tagId != '') { imp.tagid = tagId; } - if (bid.mediaType === 'video') { - var mimes = []; - var maxduration = 0; - var protocols = []; - var api = []; - - var video = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; - - mimes = utils.getBidIdParameter('mimes', bid.params); - if (mimes !== '') { - video.mimes = mimes; - } - - maxduration = utils.getBidIdParameter('maxduration', bid.params); - if (maxduration !== '') { - video.maxduration = maxduration; - } - - protocols = utils.getBidIdParameter('protocols', bid.params); - if (protocols !== '') { - video.protocols = protocols; - } - - api = utils.getBidIdParameter('api', bid.params); - if (api !== '') { - video.api = api; - } + const banner = { + format: format + }; - if (pos !== '') { - video.pos = pos; - } + if (pos != '') { + banner.pos = pos; + } - imp.video = video; - } else { - var banner = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; + imp.banner = banner; - if (pos !== '') { - banner.pos = pos; - } - imp.banner = banner; - } + // console.log(JSON.stringify(imp)); - conversantImps.push(imp); + return imp; }); - conversantBidReqs = { - 'id': utils.getUniqueIdentifierStr(), - 'imp': conversantImps, - - 'site': { - 'id': siteId, - 'mobile': document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, - 'page': page + const payload = { + id: requestId, + imp: conversantImps, + site: { + id: siteId, + mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, + page: page }, - - 'device': getDevice(), - 'at': 1 + device: getDevice(), + at: 1 }; + const payloadString = JSON.stringify(payload); + // console.log('payload', payloadString); - var url = secure ? 'https:' + conversantUrl : location.protocol + conversantUrl; - ajax(url, appendScript, JSON.stringify(conversantBidReqs), { - withCredentials: true - }); - }; - - var addEmptyBidResponses = function (placementsWithBidsBack) { - var allConversantBidRequests = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant'); - - if (allConversantBidRequests && allConversantBidRequests.bids) { - utils._each(allConversantBidRequests.bids, function (conversantBid) { - if (!utils.contains(placementsWithBidsBack, conversantBid.placementCode)) { - // Add a no-bid response for this placement. - var bid = bidfactory.createBid(2, conversantBid); - bid.bidderCode = 'conversant'; - bidmanager.addBidResponse(conversantBid.placementCode, bid); - } - }); + return { + method: 'POST', + url: URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} + * serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidResponses = []; + + // console.log('response', JSON.stringify(serverResponse)); + // console.log('request', JSON.stringify(bidRequest)); + + if (serverResponse && serverResponse.id) { + // console.log('processing serverResponse'); + serverResponse.seatbid.forEach(bidList => bidResponses = bidList.bid.reduce(parseBid, [])); } - }; - - var parseSeatbid = function (bidResponse) { - var placementsWithBidsBack = []; - utils._each(bidResponse.bid, function (conversantBid) { - var responseCPM; - var placementCode = ''; - var id = conversantBid.impid; - var bid = {}; - var responseAd; - var responseNurl; - var sizeArrayLength; - - // Bid request we sent Conversant - var bidRequested = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant').bids.find(bid => bid.bidId === id); - - if (bidRequested) { - placementCode = bidRequested.placementCode; - bidRequested.status = CONSTANTS.STATUS.GOOD; - responseCPM = parseFloat(conversantBid.price); - if (responseCPM !== 0.0) { - conversantBid.placementCode = placementCode; - placementsWithBidsBack.push(placementCode); - conversantBid.size = bidRequested.sizes; - responseAd = conversantBid.adm || ''; - responseNurl = conversantBid.nurl || ''; + // console.log('response', JSON.stringify(bidResponses)); - // Our bid! - bid = bidfactory.createBid(1, bidRequested); - bid.creative_id = conversantBid.id || ''; - bid.bidderCode = 'conversant'; - bid.cpm = responseCPM; - - if (bidRequested.mediaType === 'video') { - bid.vastUrl = responseAd; - } else { - // Track impression image onto returned html - bid.ad = responseAd + ''; - } - - sizeArrayLength = bidRequested.sizes.length; - if (sizeArrayLength === 2 && typeof bidRequested.sizes[0] === 'number' && typeof bidRequested.sizes[1] === 'number') { - bid.width = bidRequested.sizes[0]; - bid.height = bidRequested.sizes[1]; - } else { - bid.width = bidRequested.sizes[0][0]; - bid.height = bidRequested.sizes[0][1]; - } - - bidmanager.addBidResponse(placementCode, bid); - } - } - }); - addEmptyBidResponses(placementsWithBidsBack); - }; - - // Register our callback to the global object: - $$PREBID_GLOBAL$$.conversantResponse = function (conversantResponseObj, path) { - // valid object? - if (conversantResponseObj && conversantResponseObj.id) { - if (conversantResponseObj.seatbid && conversantResponseObj.seatbid.length > 0 && conversantResponseObj.seatbid[0].bid && conversantResponseObj.seatbid[0].bid.length > 0) { - utils._each(conversantResponseObj.seatbid, parseSeatbid); - } else { - // no response data for any placements - addEmptyBidResponses([]); - } - } else { - // no response data for any placements - addEmptyBidResponses([]); + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'image', + url: '//cmedia102.dev2.vcmedia.com/w/user.sync' + }]; } - // for debugging purposes - if (path) { - adloader.loadScript(path, function () { - var allConversantBidRequests = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant'); + } +} - if ($$PREBID_GLOBAL$$.conversantDebugResponse) { - $$PREBID_GLOBAL$$.conversantDebugResponse(allConversantBidRequests); - } - }); - } - }; // conversantResponse +function getDNT() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; return { - callBids: callBids + h: screen.height, + w: screen.width, + dnt: getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent }; -}; +} + +function parseBid(bidResponses, conversantBid) { + // console.log('parseBid called'); + const responseCPM = parseFloat(conversantBid.price); + if (responseCPM > 0.0 && conversantBid.impid) { + const responseAd = conversantBid.adm || ''; + const responseNurl = conversantBid.nurl || ''; + + const bid = { + requestId: conversantBid.impid, + bidderCode: BIDDER_CODE, + currency: 'USD', + cpm: responseCPM, + creativeId: conversantBid.crid || '' + }; + + bid.ad = responseAd + ''; + bid.width = conversantBid.w; + bid.height = conversantBid.h; + + // console.log('bid', JSON.stringify(bid)); + bidResponses.push(bid); + } -adaptermanager.registerBidAdapter(new ConversantAdapter(), 'conversant', { - supportedMediaTypes: ['video'] -}); + return bidResponses; +} -module.exports = ConversantAdapter; +registerBidder(spec); From 22ba2ecff98ce2625dc3cf659bec24661adaddb3 Mon Sep 17 00:00:00 2001 From: pyang Date: Fri, 6 Oct 2017 08:30:46 -0700 Subject: [PATCH 02/14] Add video support for conversant adapter --- modules/conversantBidAdapter.js | 114 +++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 40 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 5ab91614c57..dca9f1e97bc 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,15 +1,15 @@ -'use strict'; import * as utils from 'src/utils'; -// import {config} from 'src/config'; import {registerBidder} from 'src/adapters/bidderFactory'; +import { VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'conversant'; -const URL = '//cmedia102.dev2.vcmedia.com/s2s/header/24'; +const URL = '//media.msg.dotomi.com/s2s/header/24'; const VERSION = '2.2.0'; export const spec = { code: BIDDER_CODE, aliases: ['cnvr'], // short code + supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -18,6 +18,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + // console.log('did we get called?', JSON.stringify(bid)); return !!(bid.params.site_id); }, /** @@ -37,16 +38,12 @@ export const spec = { const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - const tagId = utils.getBidIdParameter('tag_id', bid.params); - const pos = utils.getBidIdParameter('position', bid.params); siteId = utils.getBidIdParameter('site_id', bid.params); secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; requestId = bid.requestId; - const format = bid.sizes.map(function(d) { - return {w: d[0], h: d[1]}; - }); + const format = convertSizes(bid.sizes); const imp = { id: bid.bidId, @@ -56,20 +53,25 @@ export const spec = { displaymanaerver: VERSION }; - if (tagId != '') { - imp.tagid = tagId; - } + copyOptProperty(bid.params, 'tag_id', imp, 'tagid'); - const banner = { - format: format - }; + if (isVideoRequest(bid)) { + const video = {format: format}; - if (pos != '') { - banner.pos = pos; - } + copyOptProperty(bid.params, 'position', video, 'pos'); + copyOptProperty(bid.params, 'mimes', video); + copyOptProperty(bid.params, 'maxduration', video); + copyOptProperty(bid.params, 'protocols', video); + copyOptProperty(bid.params, 'api', video); + + imp.video = video; + } else { + const banner = {format: format}; - imp.banner = banner; + copyOptProperty(bid.params, 'position', banner, 'pos'); + imp.banner = banner; + } // console.log(JSON.stringify(imp)); return imp; @@ -93,6 +95,7 @@ export const spec = { method: 'POST', url: URL, data: payloadString, + payload: payload }; }, /** @@ -104,13 +107,46 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { let bidResponses = []; + const requestMap = {}; + + bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); // console.log('response', JSON.stringify(serverResponse)); // console.log('request', JSON.stringify(bidRequest)); + // console.log('map', JSON.stringify(requestMap)); if (serverResponse && serverResponse.id) { // console.log('processing serverResponse'); - serverResponse.seatbid.forEach(bidList => bidResponses = bidList.bid.reduce(parseBid, [])); + serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { + const responseCPM = parseFloat(conversantBid.price); + if (responseCPM > 0.0 && conversantBid.impid) { + const responseAd = conversantBid.adm || ''; + const responseNurl = conversantBid.nurl || ''; + const request = requestMap[conversantBid.impid]; + + const bid = { + requestId: conversantBid.impid, + bidderCode: BIDDER_CODE, + currency: 'USD', + cpm: responseCPM, + creativeId: conversantBid.crid || '' + }; + + if (request.video) { + bid.vastUrl = responseAd; + // bid.descriptionUrl = responseAd; + bid.mediaType = 'video'; + } else { + bid.ad = responseAd + ''; + } + + bid.width = conversantBid.w; + bid.height = conversantBid.h; + + // console.log('bid', JSON.stringify(bid)); + bidResponses.push(bid); + } + })); } // console.log('response', JSON.stringify(bidResponses)); @@ -143,30 +179,28 @@ function getDevice() { }; } -function parseBid(bidResponses, conversantBid) { - // console.log('parseBid called'); - const responseCPM = parseFloat(conversantBid.price); - if (responseCPM > 0.0 && conversantBid.impid) { - const responseAd = conversantBid.adm || ''; - const responseNurl = conversantBid.nurl || ''; - - const bid = { - requestId: conversantBid.impid, - bidderCode: BIDDER_CODE, - currency: 'USD', - cpm: responseCPM, - creativeId: conversantBid.crid || '' - }; +function convertSizes(bidSizes) { + let format; - bid.ad = responseAd + ''; - bid.width = conversantBid.w; - bid.height = conversantBid.h; - - // console.log('bid', JSON.stringify(bid)); - bidResponses.push(bid); + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + format = [{w: bidSizes[0], h: bidSizes[1]}]; + } else { + format = bidSizes.map(d => { return {w: d[0], h: d[1]}; }); } - return bidResponses; + return format; +} + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); +} + +function copyOptProperty(src, srcName, dst, dstName) { + dstName = dstName || srcName; + const obj = utils.getBidIdParameter(srcName, src); + if (obj !== '') { + dst[dstName] = obj; + } } registerBidder(spec); From f1429f6e18763be71bc50e2d83289b72249764ff Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 10 Oct 2017 15:26:53 -0700 Subject: [PATCH 03/14] Add tests and md --- modules/conversantBidAdapter.js | 50 +- modules/conversantBidAdapter.md | 41 ++ .../spec/modules/conversantBidAdapter_spec.js | 597 +++++++----------- 3 files changed, 300 insertions(+), 388 deletions(-) create mode 100644 modules/conversantBidAdapter.md diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index dca9f1e97bc..0c8ca1d0c1a 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -4,36 +4,33 @@ import { VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'conversant'; const URL = '//media.msg.dotomi.com/s2s/header/24'; +const SYNC_URL = '//media.msg.dotomi.com/w/user.sync'; const VERSION = '2.2.0'; export const spec = { code: BIDDER_CODE, - aliases: ['cnvr'], // short code + aliases: ['conversant'], // short code supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. * - * @param {BidRequest} - * bid The bid params to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - // console.log('did we get called?', JSON.stringify(bid)); - return !!(bid.params.site_id); + return !!(bid && bid.params && bid.params.site_id); }, /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - - * an array of bids + * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests) { - // console.log('hello world ', JSON.stringify(validBidRequests)); const loc = utils.getTopWindowLocation(); + const page = loc.pathname + loc.search + loc.hash; let siteId = ''; let secure = 0; - let page = loc.pathname + loc.search + loc.hash; let requestId = ''; const conversantImps = validBidRequests.map(function(bid) { @@ -72,7 +69,6 @@ export const spec = { imp.banner = banner; } - // console.log(JSON.stringify(imp)); return imp; }); @@ -88,9 +84,8 @@ export const spec = { device: getDevice(), at: 1 }; - const payloadString = JSON.stringify(payload); - // console.log('payload', payloadString); + const payloadString = JSON.stringify(payload); return { method: 'POST', url: URL, @@ -101,22 +96,17 @@ export const spec = { /** * Unpack the response from the server into a list of bids. * - * @param {*} - * serverResponse A successful response from the server. + * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - let bidResponses = []; + const bidResponses = []; const requestMap = {}; + const currency = serverResponse.cur || 'USD'; bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); - // console.log('response', JSON.stringify(serverResponse)); - // console.log('request', JSON.stringify(bidRequest)); - // console.log('map', JSON.stringify(requestMap)); - if (serverResponse && serverResponse.id) { - // console.log('processing serverResponse'); serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { const responseCPM = parseFloat(conversantBid.price); if (responseCPM > 0.0 && conversantBid.impid) { @@ -127,37 +117,37 @@ export const spec = { const bid = { requestId: conversantBid.impid, bidderCode: BIDDER_CODE, - currency: 'USD', + currency: currency, cpm: responseCPM, creativeId: conversantBid.crid || '' }; if (request.video) { bid.vastUrl = responseAd; - // bid.descriptionUrl = responseAd; bid.mediaType = 'video'; + + if (request.video.format.length >= 1) { + bid.width = request.video.format[0].w; + bid.height = request.video.format[0].h; + } } else { bid.ad = responseAd + ''; + bid.width = conversantBid.w; + bid.height = conversantBid.h; } - bid.width = conversantBid.w; - bid.height = conversantBid.h; - - // console.log('bid', JSON.stringify(bid)); bidResponses.push(bid); } })); } - // console.log('response', JSON.stringify(bidResponses)); - return bidResponses; }, getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { + if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: '//cmedia102.dev2.vcmedia.com/w/user.sync' + url: SYNC_URL }]; } } diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md new file mode 100644 index 00000000000..3d7f217bd7a --- /dev/null +++ b/modules/conversantBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +- Module Name: Conversant Bidder Adapter +- Module Type: Bidder Adapter +- Maintainer: pyang@conversantmedia.com + +# Description + +Module that connects to Conversant's demand sources. Supports banners and videos. + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: "conversant", + params: { + site_id: '108060' + } + }] + },{ + code: 'video-test-div', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: "conversant", + params: { + site_id: '88563', + api: [2], + protocols: [1, 2], + mimes: ['video/mp4'] + } + }] + }]; +``` \ No newline at end of file diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 57cd9411e66..704ecf67c15 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,376 +1,257 @@ -var expect = require('chai').expect; +import {expect} from 'chai'; +import {spec} from 'modules/conversantBidAdapter'; +import * as utils from 'src/utils'; + var Adapter = require('modules/conversantBidAdapter'); var bidManager = require('src/bidmanager'); -describe('Conversant adapter tests', function () { - var addBidResponseSpy; - var adapter; - - var bidderRequest = { - bidderCode: 'conversant', - bids: [ - { - bidId: 'bidId1', - bidder: 'conversant', - placementCode: 'div1', - sizes: [[300, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: 'tagid-1', - secure: false +describe('Conversant adapter tests', function() { + const siteId = '108060'; + + const bidRequests = [ + { + bidder: 'conversant', + params: { + site_id: siteId, + position: 1, + tag_id: 'tagid-1', + secure: false, + bidfloor: 0.5 + }, + placementCode: 'pcode000', + transactionId: 'tx000', + sizes: [[300, 250]], + bidId: 'bid000', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + secure: false + }, + placementCode: 'pcode001', + transactionId: 'tx001', + sizes: [[468, 60]], + bidId: 'bid001', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + position: 2, + tag_id: '', + secure: false + }, + placementCode: 'pcode002', + transactionId: 'tx002', + sizes: [[300, 600], [160, 600]], + bidId: 'bid002', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + api: [2], + protocols: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30 + }, + mediaTypes: { + video: { + context: 'instream' } + }, + placementCode: 'pcode003', + transactionId: 'tx003', + sizes: [640, 480], + bidId: 'bid003', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }]; + + const bidResponses = { + id: 'req000', + seatbid: [{ + bid: [{ + nurl: 'notify000', + adm: 'markup000', + crid: '1000', + impid: 'bid000', + price: 0.99, + w: 300, + h: 250, + adomain: ['https://example.com'], + id: 'bid000' }, { - bidId: 'bidId2', - bidder: 'conversant', - placementCode: 'div2', - sizes: [[300, 600]], - params: { - site_id: '87293', - secure: false - } + impid: 'bid001', + price: 0.00000, + id: 'bid001' }, { - bidId: 'bidId3', - bidder: 'conversant', - placementCode: 'div3', - sizes: [[300, 600], [160, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: '', - secure: false - } + nurl: 'notify002', + adm: 'markup002', + crid: '1002', + impid: 'bid002', + price: 2.99, + w: 300, + h: 600, + adomain: ['https://example.com'], + id: 'bid002' }, { - bidId: 'bidId4', - bidder: 'conversant', - placementCode: 'div4', - mediaType: 'video', - sizes: [[480, 480]], - params: { - site_id: '89192', - pos: 1, - tagid: 'tagid-4', - secure: false - } - } - ] + nurl: 'notify003', + adm: 'markup003', + crid: '1003', + impid: 'bid003', + price: 3.99, + adomain: ['https://example.com'], + id: 'bid003' + }] + }] }; - it('The Conversant response should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.conversantResponse).to.exist.and.to.be.a('function'); + it('Verify basic properties', function() { + expect(spec.code).to.equal('conversant'); + expect(spec.aliases).to.be.an('array').with.lengthOf(1); + expect(spec.aliases[0]).to.equal('conversant'); + expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('video'); }); - describe('Should submit bid responses correctly', function () { - beforeEach(function () { - addBidResponseSpy = sinon.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter = new Adapter(); - }); - - afterEach(function () { - addBidResponseSpy.restore(); - }); - - it('Should correctly submit valid and empty bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0 - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - // in this case, the valid bid (div2) is submitted before the empty bids (div1, div3) - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.22); - expect(firstBid.ad).to.equal('adm2' + ''); - expect(placementCode1).to.equal('div2'); - - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(placementCode2).to.equal('div1'); - - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit bids with statuses of 2 to the bid manager for empty bid responses', function () { - $$PREBID_GLOBAL$$.conversantResponse({id: 1, seatbid: []}); - - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - - expect(placementCode1).to.equal('div1'); - expect(firstBid.getStatusCode()).to.equal(2); - expect(firstBid.bidderCode).to.equal('conversant'); - - expect(placementCode2).to.equal('div2'); - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - - expect(placementCode3).to.equal('div3'); - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit valid bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 33333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; + it('Verify user syncs', function() { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({iframeEnabled: true})).to.be.undefined; + expect(spec.getUserSyncs({pixelEnabled: false})).to.be.undefined; - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.11); - expect(firstBid.ad).to.equal('adm' + ''); - expect(placementCode1).to.equal('div1'); - - expect(secondBid.getStatusCode()).to.equal(1); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(secondBid.cpm).to.equal(0.22); - expect(secondBid.ad).to.equal('adm2' + ''); - expect(placementCode2).to.equal('div2'); - - expect(thirdBid.getStatusCode()).to.equal(1); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(thirdBid.cpm).to.equal(0.33); - expect(thirdBid.ad).to.equal('adm3' + ''); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit video bid responses correctly.', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId4', - price: 0.11, - nurl: 'imp_tracker', - adm: 'vasturl' - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var videoBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode = addBidResponseSpy.getCall(0).args[0]; - - expect(videoBid.getStatusCode()).to.equal(1); - expect(videoBid.bidderCode).to.equal('conversant'); - expect(videoBid.cpm).to.equal(0.11); - expect(videoBid.vastUrl).to.equal('vasturl'); - expect(placementCode).to.equal('div4'); - }) + const syncs = spec.getUserSyncs({pixelEnabled: true}); + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('//media.msg.dotomi.com/w/user.sync'); }); - describe('Should submit the correct headers in the xhr', function () { - var server, - adapter; - - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); - - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); - - it('Should contain valid request header properties', function () { - adapter.callBids(bidderRequest); - server.respond(); - - var request = server.requests[0]; - expect(request.requestBody).to.not.be.empty; - }); + it('Verify isBidRequestValid', function() { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {site_id: ''}})).to.be.false; + expect(spec.isBidRequestValid({params: {site_id: '123'}})).to.be.true; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; }); - describe('Should create valid bid requests.', function () { - var server, - adapter; - - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); - - it('Should create valid bid requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[0].banner.format[0].w).to.equal(300); - expect(request.imp[0].banner.format[0].h).to.equal(600); - expect(request.imp[0].tagid).to.equal('tagid-1'); - expect(request.imp[0].banner.pos).to.equal(1); - expect(request.imp[0].secure).to.equal(0); - expect(request.site.id).to.equal('89192'); - }); - - it('Should not pass empty or missing optional parameters on requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); - - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[1].tagid).to.equal(undefined); - expect(request.imp[2].tagid).to.equal(undefined); - expect(request.imp[1].pos).to.equal(undefined); - }); - - it('Should create the format objects correctly.', function () { - adapter.callBids(bidderRequest); - server.respond(); + it('Verify buildRequest', function() { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('//media.msg.dotomi.com/s2s/header/24'); + const parsed = JSON.parse(request.data); + + expect(parsed).to.deep.equal(request.payload); + + expect(parsed).to.have.property('id', 'req000'); + expect(parsed).to.have.property('at', 1); + expect(parsed).to.have.property('imp'); + expect(parsed.imp).to.be.an('array').with.lengthOf(4); + + expect(parsed.imp[0]).to.have.property('id', 'bid000'); + expect(parsed.imp[0]).to.have.property('secure', 0); + expect(parsed.imp[0]).to.have.property('bidfloor', 0.5); + expect(parsed.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[0]).to.have.property('tagid', 'tagid-1'); + expect(parsed.imp[0]).to.have.property('banner'); + expect(parsed.imp[0].banner).to.have.property('pos', 1); + expect(parsed.imp[0].banner).to.have.property('format'); + expect(parsed.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + expect(parsed.imp[0]).to.not.have.property('video'); + + expect(parsed.imp[1]).to.have.property('id', 'bid001'); + expect(parsed.imp[1]).to.have.property('secure', 0); + expect(parsed.imp[1]).to.have.property('bidfloor', 0); + expect(parsed.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[1]).to.not.have.property('tagid'); + expect(parsed.imp[1]).to.have.property('banner'); + expect(parsed.imp[1].banner).to.not.have.property('pos'); + expect(parsed.imp[1].banner).to.have.property('format'); + expect(parsed.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); + + expect(parsed.imp[2]).to.have.property('id', 'bid002'); + expect(parsed.imp[2]).to.have.property('secure', 0); + expect(parsed.imp[2]).to.have.property('bidfloor', 0); + expect(parsed.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[2]).to.have.property('banner'); + expect(parsed.imp[2].banner).to.have.property('pos', 2); + expect(parsed.imp[2].banner).to.have.property('format'); + expect(parsed.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); + + expect(parsed.imp[3]).to.have.property('id', 'bid003'); + expect(parsed.imp[3]).to.have.property('secure', 0); + expect(parsed.imp[3]).to.have.property('bidfloor', 0); + expect(parsed.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[3]).to.not.have.property('tagid'); + expect(parsed.imp[3]).to.have.property('video'); + expect(parsed.imp[3].video).to.not.have.property('pos'); + expect(parsed.imp[3].video).to.have.property('format'); + expect(parsed.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); + expect(parsed.imp[3].video).to.have.property('mimes'); + expect(parsed.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(parsed.imp[3].video).to.have.property('protocols'); + expect(parsed.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(parsed.imp[3].video).to.have.property('api'); + expect(parsed.imp[3].video.api).to.deep.equal([2]); + expect(parsed.imp[3].video).to.have.property('maxduration', 30); + expect(parsed.imp[3]).to.not.have.property('banner'); + + expect(parsed).to.have.property('site'); + expect(parsed.site).to.have.property('id', siteId); + expect(parsed.site).to.have.property('mobile').that.is.oneOf([0, 1]); + const loc = utils.getTopWindowLocation(); + const page = loc.pathname + loc.search + loc.hash; + expect(parsed.site).to.have.property('page', page); + + expect(parsed).to.have.property('device'); + expect(parsed.device).to.have.property('w', screen.width); + expect(parsed.device).to.have.property('h', screen.height); + expect(parsed.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(parsed.device).to.have.property('ua', navigator.userAgent); + }); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[2].banner.format.length).to.equal(2); - expect(request.imp[2].banner.format[0].w).to.equal(300); - expect(request.imp[2].banner.format[1].w).to.equal(160); - }); + it('Verify interpretResponse', function() { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.lengthOf(3); + + let bid = response[0]; + expect(bid).to.have.property('requestId', 'bid000'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 0.99); + expect(bid).to.have.property('creativeId', '1000'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 250); + expect(bid).to.have.property('ad', 'markup000'); + + // There is no bid001 because cpm is $0 + + bid = response[1]; + expect(bid).to.have.property('requestId', 'bid002'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 2.99); + expect(bid).to.have.property('creativeId', '1002'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 600); + expect(bid).to.have.property('ad', 'markup002'); + + bid = response[2]; + expect(bid).to.have.property('requestId', 'bid003'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 3.99); + expect(bid).to.have.property('creativeId', '1003'); + expect(bid).to.have.property('width', 640); + expect(bid).to.have.property('height', 480); + expect(bid).to.have.property('vastUrl', 'markup003'); + expect(bid).to.have.property('mediaType', 'video'); }); -}); +}) From 1eda1eac20d76a8174bba4d827665d767bb69f2a Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 10 Oct 2017 15:28:07 -0700 Subject: [PATCH 04/14] Update conversant contact address --- modules/conversantBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index 3d7f217bd7a..1afdad6d544 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -2,7 +2,7 @@ - Module Name: Conversant Bidder Adapter - Module Type: Bidder Adapter -- Maintainer: pyang@conversantmedia.com +- Maintainer: mediapsr@conversantmedia.com # Description From 7e4c476831420a0d89d51e17884a0a5be6c8fb09 Mon Sep 17 00:00:00 2001 From: pyang Date: Wed, 11 Oct 2017 10:44:40 -0700 Subject: [PATCH 05/14] Return data object in buildRequests without converting it to a string --- modules/conversantBidAdapter.js | 57 ++++++-- .../spec/modules/conversantBidAdapter_spec.js | 130 +++++++++--------- 2 files changed, 110 insertions(+), 77 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 0c8ca1d0c1a..37442440322 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -13,18 +13,18 @@ export const spec = { 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. + * + * @param {BidRequest} bid - The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { return !!(bid && bid.params && bid.params.site_id); }, /** * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(validBidRequests) { const loc = utils.getTopWindowLocation(); @@ -85,17 +85,15 @@ export const spec = { at: 1 }; - const payloadString = JSON.stringify(payload); return { method: 'POST', url: URL, - data: payloadString, - payload: payload + data: payload, }; }, /** * Unpack the response from the server into a list of bids. - * + * * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ @@ -104,7 +102,7 @@ export const spec = { const requestMap = {}; const currency = serverResponse.cur || 'USD'; - bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); + bidRequest.data.imp.forEach(imp => requestMap[imp.id] = imp); if (serverResponse && serverResponse.id) { serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { @@ -143,6 +141,12 @@ export const spec = { return bidResponses; }, + /** + * Return use sync info + * + * @param {SyncOptions} syncOptions - Info about usersyncs that the adapter should obey + * @return {UserSync} Adapter sync type and url + */ getUserSyncs: function(syncOptions) { if (syncOptions.pixelEnabled) { return [{ @@ -153,10 +157,20 @@ export const spec = { } } +/** + * Determine do-not-track state + * + * @returns {boolean} + */ function getDNT() { return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; } +/** + * Return openrtb device object that includes ua, width, and height. + * + * @returns {Device} Openrtb device object + */ function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { @@ -169,6 +183,13 @@ function getDevice() { }; } +/** + * Convert arrays of widths and heights to an array of objects with w and h properties. [[300, 250], + * [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] + * + * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights + * @returns {object[]} Array of objects with w and h + */ function convertSizes(bidSizes) { let format; @@ -181,10 +202,24 @@ function convertSizes(bidSizes) { return format; } +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ function isVideoRequest(bid) { return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); } +/** + * Copy property if exists from src to dst + * + * @param {object} src + * @param {string} srcName + * @param {object} dst + * @param {string} [dstName] - Optional. If not specified then srcName is used. + */ function copyOptProperty(src, srcName, dst, dstName) { dstName = dstName || srcName; const obj = utils.getBidIdParameter(srcName, src); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 704ecf67c15..d39bccf5006 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -145,75 +145,73 @@ describe('Conversant adapter tests', function() { const request = spec.buildRequests(bidRequests); expect(request.method).to.equal('POST'); expect(request.url).to.equal('//media.msg.dotomi.com/s2s/header/24'); - const parsed = JSON.parse(request.data); - - expect(parsed).to.deep.equal(request.payload); - - expect(parsed).to.have.property('id', 'req000'); - expect(parsed).to.have.property('at', 1); - expect(parsed).to.have.property('imp'); - expect(parsed.imp).to.be.an('array').with.lengthOf(4); - - expect(parsed.imp[0]).to.have.property('id', 'bid000'); - expect(parsed.imp[0]).to.have.property('secure', 0); - expect(parsed.imp[0]).to.have.property('bidfloor', 0.5); - expect(parsed.imp[0]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[0]).to.have.property('tagid', 'tagid-1'); - expect(parsed.imp[0]).to.have.property('banner'); - expect(parsed.imp[0].banner).to.have.property('pos', 1); - expect(parsed.imp[0].banner).to.have.property('format'); - expect(parsed.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - expect(parsed.imp[0]).to.not.have.property('video'); - - expect(parsed.imp[1]).to.have.property('id', 'bid001'); - expect(parsed.imp[1]).to.have.property('secure', 0); - expect(parsed.imp[1]).to.have.property('bidfloor', 0); - expect(parsed.imp[1]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[1]).to.not.have.property('tagid'); - expect(parsed.imp[1]).to.have.property('banner'); - expect(parsed.imp[1].banner).to.not.have.property('pos'); - expect(parsed.imp[1].banner).to.have.property('format'); - expect(parsed.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); - - expect(parsed.imp[2]).to.have.property('id', 'bid002'); - expect(parsed.imp[2]).to.have.property('secure', 0); - expect(parsed.imp[2]).to.have.property('bidfloor', 0); - expect(parsed.imp[2]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[2]).to.have.property('banner'); - expect(parsed.imp[2].banner).to.have.property('pos', 2); - expect(parsed.imp[2].banner).to.have.property('format'); - expect(parsed.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); - - expect(parsed.imp[3]).to.have.property('id', 'bid003'); - expect(parsed.imp[3]).to.have.property('secure', 0); - expect(parsed.imp[3]).to.have.property('bidfloor', 0); - expect(parsed.imp[3]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[3]).to.not.have.property('tagid'); - expect(parsed.imp[3]).to.have.property('video'); - expect(parsed.imp[3].video).to.not.have.property('pos'); - expect(parsed.imp[3].video).to.have.property('format'); - expect(parsed.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); - expect(parsed.imp[3].video).to.have.property('mimes'); - expect(parsed.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(parsed.imp[3].video).to.have.property('protocols'); - expect(parsed.imp[3].video.protocols).to.deep.equal([1, 2]); - expect(parsed.imp[3].video).to.have.property('api'); - expect(parsed.imp[3].video.api).to.deep.equal([2]); - expect(parsed.imp[3].video).to.have.property('maxduration', 30); - expect(parsed.imp[3]).to.not.have.property('banner'); - - expect(parsed).to.have.property('site'); - expect(parsed.site).to.have.property('id', siteId); - expect(parsed.site).to.have.property('mobile').that.is.oneOf([0, 1]); + const payload = request.data; + + expect(payload).to.have.property('id', 'req000'); + expect(payload).to.have.property('at', 1); + expect(payload).to.have.property('imp'); + expect(payload.imp).to.be.an('array').with.lengthOf(4); + + expect(payload.imp[0]).to.have.property('id', 'bid000'); + expect(payload.imp[0]).to.have.property('secure', 0); + expect(payload.imp[0]).to.have.property('bidfloor', 0.5); + expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); + expect(payload.imp[0]).to.have.property('banner'); + expect(payload.imp[0].banner).to.have.property('pos', 1); + expect(payload.imp[0].banner).to.have.property('format'); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + expect(payload.imp[0]).to.not.have.property('video'); + + expect(payload.imp[1]).to.have.property('id', 'bid001'); + expect(payload.imp[1]).to.have.property('secure', 0); + expect(payload.imp[1]).to.have.property('bidfloor', 0); + expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[1]).to.not.have.property('tagid'); + expect(payload.imp[1]).to.have.property('banner'); + expect(payload.imp[1].banner).to.not.have.property('pos'); + expect(payload.imp[1].banner).to.have.property('format'); + expect(payload.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); + + expect(payload.imp[2]).to.have.property('id', 'bid002'); + expect(payload.imp[2]).to.have.property('secure', 0); + expect(payload.imp[2]).to.have.property('bidfloor', 0); + expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[2]).to.have.property('banner'); + expect(payload.imp[2].banner).to.have.property('pos', 2); + expect(payload.imp[2].banner).to.have.property('format'); + expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); + + expect(payload.imp[3]).to.have.property('id', 'bid003'); + expect(payload.imp[3]).to.have.property('secure', 0); + expect(payload.imp[3]).to.have.property('bidfloor', 0); + expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[3]).to.not.have.property('tagid'); + expect(payload.imp[3]).to.have.property('video'); + expect(payload.imp[3].video).to.not.have.property('pos'); + expect(payload.imp[3].video).to.have.property('format'); + expect(payload.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); + expect(payload.imp[3].video).to.have.property('mimes'); + expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[3].video).to.have.property('protocols'); + expect(payload.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(payload.imp[3].video).to.have.property('api'); + expect(payload.imp[3].video.api).to.deep.equal([2]); + expect(payload.imp[3].video).to.have.property('maxduration', 30); + expect(payload.imp[3]).to.not.have.property('banner'); + + expect(payload).to.have.property('site'); + expect(payload.site).to.have.property('id', siteId); + expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); const loc = utils.getTopWindowLocation(); const page = loc.pathname + loc.search + loc.hash; - expect(parsed.site).to.have.property('page', page); + expect(payload.site).to.have.property('page', page); - expect(parsed).to.have.property('device'); - expect(parsed.device).to.have.property('w', screen.width); - expect(parsed.device).to.have.property('h', screen.height); - expect(parsed.device).to.have.property('dnt').that.is.oneOf([0, 1]); - expect(parsed.device).to.have.property('ua', navigator.userAgent); + expect(payload).to.have.property('device'); + expect(payload.device).to.have.property('w', screen.width); + expect(payload.device).to.have.property('h', screen.height); + expect(payload.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(payload.device).to.have.property('ua', navigator.userAgent); }); it('Verify interpretResponse', function() { From 35e1f79aa07c3c6fc9f6db7d1c730a7f7efc9e52 Mon Sep 17 00:00:00 2001 From: pyang Date: Thu, 5 Oct 2017 09:40:54 -0700 Subject: [PATCH 06/14] Conversant adapter initial support for prebid 1.0 --- modules/conversantBidAdapter.js | 386 ++++++++++++-------------------- 1 file changed, 140 insertions(+), 246 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index d51008559f2..5ab91614c57 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,278 +1,172 @@ 'use strict'; -var VERSION = '2.1.0'; -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var ajax = require('src/ajax').ajax; -var adaptermanager = require('src/adaptermanager'); - -/** - * Adapter for requesting bids from Conversant - */ -var ConversantAdapter = function () { - var w = window; - var n = navigator; - - // production endpoint - var conversantUrl = '//media.msg.dotomi.com/s2s/header/24?callback=$$PREBID_GLOBAL$$.conversantResponse'; - - // SSAPI returns JSONP with window.pbjs.conversantResponse as the cb - var appendScript = function (code) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.className = 'cnvr-response'; - - try { - script.appendChild(document.createTextNode(code)); - document.getElementsByTagName('head')[0].appendChild(script); - } catch (e) { - script.text = code; - document.getElementsByTagName('head')[0].appendChild(script); - } - }; - - var getDNT = function () { - return n.doNotTrack === '1' || w.doNotTrack === '1' || n.msDoNotTrack === '1' || n.doNotTrack === 'yes'; - }; - - var getDevice = function () { - const language = n.language ? 'language' : 'userLanguage'; - return { - h: screen.height, - w: screen.width, - dnt: getDNT() ? 1 : 0, - language: n[language].split('-')[0], - make: n.vendor ? n.vendor : '', - ua: n.userAgent - }; - }; - - var callBids = function (params) { - var conversantBids = params.bids || []; - requestBids(conversantBids); - }; - - var requestBids = function (bidReqs) { - // build bid request object - var page = location.pathname + location.search + location.hash; - var siteId = ''; - var conversantImps = []; - var conversantBidReqs; - var secure = 0; - - // build impression array for conversant - utils._each(bidReqs, function (bid) { - var bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - var adW = 0; - var adH = 0; - var format; - var tagId; - var pos; - var imp; - +import * as utils from 'src/utils'; +// import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'conversant'; +const URL = '//cmedia102.dev2.vcmedia.com/s2s/header/24'; +const VERSION = '2.2.0'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['cnvr'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} + * bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.site_id); + }, + /** + * 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) { + // console.log('hello world ', JSON.stringify(validBidRequests)); + const loc = utils.getTopWindowLocation(); + let siteId = ''; + let secure = 0; + let page = loc.pathname + loc.search + loc.hash; + let requestId = ''; + + const conversantImps = validBidRequests.map(function(bid) { + const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const tagId = utils.getBidIdParameter('tag_id', bid.params); + const pos = utils.getBidIdParameter('position', bid.params); + + siteId = utils.getBidIdParameter('site_id', bid.params); secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; - siteId = utils.getBidIdParameter('site_id', bid.params) + ''; - tagId = utils.getBidIdParameter('tag_id', bid.params); - pos = utils.getBidIdParameter('position', bid.params); - - // Allow sizes to be overridden per placement - var bidSizes = Array.isArray(bid.params.sizes) ? bid.params.sizes : bid.sizes; + requestId = bid.requestId; - if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - adW = bidSizes[0]; - adH = bidSizes[1]; - } else { - format = []; - utils._each(bidSizes, function (bidSize) { - format.push({ - w: bidSize[0], - h: bidSize[1] - }); - }); - } + const format = bid.sizes.map(function(d) { + return {w: d[0], h: d[1]}; + }); - imp = { + const imp = { id: bid.bidId, secure: secure, bidfloor: bidfloor || 0, displaymanager: 'Prebid.js', - displaymanagerver: VERSION + displaymanaerver: VERSION }; - if (tagId !== '') { + if (tagId != '') { imp.tagid = tagId; } - if (bid.mediaType === 'video') { - var mimes = []; - var maxduration = 0; - var protocols = []; - var api = []; - - var video = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; - - mimes = utils.getBidIdParameter('mimes', bid.params); - if (mimes !== '') { - video.mimes = mimes; - } - - maxduration = utils.getBidIdParameter('maxduration', bid.params); - if (maxduration !== '') { - video.maxduration = maxduration; - } - - protocols = utils.getBidIdParameter('protocols', bid.params); - if (protocols !== '') { - video.protocols = protocols; - } - - api = utils.getBidIdParameter('api', bid.params); - if (api !== '') { - video.api = api; - } + const banner = { + format: format + }; - if (pos !== '') { - video.pos = pos; - } + if (pos != '') { + banner.pos = pos; + } - imp.video = video; - } else { - var banner = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; + imp.banner = banner; - if (pos !== '') { - banner.pos = pos; - } - imp.banner = banner; - } + // console.log(JSON.stringify(imp)); - conversantImps.push(imp); + return imp; }); - conversantBidReqs = { - 'id': utils.getUniqueIdentifierStr(), - 'imp': conversantImps, - - 'site': { - 'id': siteId, - 'mobile': document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, - 'page': page + const payload = { + id: requestId, + imp: conversantImps, + site: { + id: siteId, + mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, + page: page }, - - 'device': getDevice(), - 'at': 1 + device: getDevice(), + at: 1 }; + const payloadString = JSON.stringify(payload); + // console.log('payload', payloadString); - var url = secure ? 'https:' + conversantUrl : location.protocol + conversantUrl; - ajax(url, appendScript, JSON.stringify(conversantBidReqs), { - withCredentials: true - }); - }; - - var addEmptyBidResponses = function (placementsWithBidsBack) { - var allConversantBidRequests = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant'); - - if (allConversantBidRequests && allConversantBidRequests.bids) { - utils._each(allConversantBidRequests.bids, function (conversantBid) { - if (!utils.contains(placementsWithBidsBack, conversantBid.placementCode)) { - // Add a no-bid response for this placement. - var bid = bidfactory.createBid(2, conversantBid); - bid.bidderCode = 'conversant'; - bidmanager.addBidResponse(conversantBid.placementCode, bid); - } - }); + return { + method: 'POST', + url: URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} + * serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidResponses = []; + + // console.log('response', JSON.stringify(serverResponse)); + // console.log('request', JSON.stringify(bidRequest)); + + if (serverResponse && serverResponse.id) { + // console.log('processing serverResponse'); + serverResponse.seatbid.forEach(bidList => bidResponses = bidList.bid.reduce(parseBid, [])); } - }; - - var parseSeatbid = function (bidResponse) { - var placementsWithBidsBack = []; - utils._each(bidResponse.bid, function (conversantBid) { - var responseCPM; - var placementCode = ''; - var id = conversantBid.impid; - var bid = {}; - var responseAd; - var responseNurl; - var sizeArrayLength; - - // Bid request we sent Conversant - var bidRequested = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant').bids.find(bid => bid.bidId === id); - - if (bidRequested) { - placementCode = bidRequested.placementCode; - bidRequested.status = CONSTANTS.STATUS.GOOD; - responseCPM = parseFloat(conversantBid.price); - if (responseCPM !== 0.0) { - conversantBid.placementCode = placementCode; - placementsWithBidsBack.push(placementCode); - conversantBid.size = bidRequested.sizes; - responseAd = conversantBid.adm || ''; - responseNurl = conversantBid.nurl || ''; + // console.log('response', JSON.stringify(bidResponses)); - // Our bid! - bid = bidfactory.createBid(1, bidRequested); - bid.creative_id = conversantBid.id || ''; - bid.bidderCode = 'conversant'; - bid.cpm = responseCPM; - - if (bidRequested.mediaType === 'video') { - bid.vastUrl = responseAd; - } else { - // Track impression image onto returned html - bid.ad = responseAd + ''; - } - - sizeArrayLength = bidRequested.sizes.length; - if (sizeArrayLength === 2 && typeof bidRequested.sizes[0] === 'number' && typeof bidRequested.sizes[1] === 'number') { - bid.width = bidRequested.sizes[0]; - bid.height = bidRequested.sizes[1]; - } else { - bid.width = bidRequested.sizes[0][0]; - bid.height = bidRequested.sizes[0][1]; - } - - bidmanager.addBidResponse(placementCode, bid); - } - } - }); - addEmptyBidResponses(placementsWithBidsBack); - }; - - // Register our callback to the global object: - $$PREBID_GLOBAL$$.conversantResponse = function (conversantResponseObj, path) { - // valid object? - if (conversantResponseObj && conversantResponseObj.id) { - if (conversantResponseObj.seatbid && conversantResponseObj.seatbid.length > 0 && conversantResponseObj.seatbid[0].bid && conversantResponseObj.seatbid[0].bid.length > 0) { - utils._each(conversantResponseObj.seatbid, parseSeatbid); - } else { - // no response data for any placements - addEmptyBidResponses([]); - } - } else { - // no response data for any placements - addEmptyBidResponses([]); + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'image', + url: '//cmedia102.dev2.vcmedia.com/w/user.sync' + }]; } - // for debugging purposes - if (path) { - adloader.loadScript(path, function () { - var allConversantBidRequests = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant'); + } +} - if ($$PREBID_GLOBAL$$.conversantDebugResponse) { - $$PREBID_GLOBAL$$.conversantDebugResponse(allConversantBidRequests); - } - }); - } - }; // conversantResponse +function getDNT() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; return { - callBids: callBids + h: screen.height, + w: screen.width, + dnt: getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent }; -}; +} + +function parseBid(bidResponses, conversantBid) { + // console.log('parseBid called'); + const responseCPM = parseFloat(conversantBid.price); + if (responseCPM > 0.0 && conversantBid.impid) { + const responseAd = conversantBid.adm || ''; + const responseNurl = conversantBid.nurl || ''; + + const bid = { + requestId: conversantBid.impid, + bidderCode: BIDDER_CODE, + currency: 'USD', + cpm: responseCPM, + creativeId: conversantBid.crid || '' + }; + + bid.ad = responseAd + ''; + bid.width = conversantBid.w; + bid.height = conversantBid.h; + + // console.log('bid', JSON.stringify(bid)); + bidResponses.push(bid); + } -adaptermanager.registerBidAdapter(new ConversantAdapter(), 'conversant', { - supportedMediaTypes: ['video'] -}); + return bidResponses; +} -module.exports = ConversantAdapter; +registerBidder(spec); From 09cb809e4dd2ee1a4f8b06a6cd10d5010effc529 Mon Sep 17 00:00:00 2001 From: pyang Date: Fri, 6 Oct 2017 08:30:46 -0700 Subject: [PATCH 07/14] Add video support for conversant adapter --- modules/conversantBidAdapter.js | 114 +++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 40 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 5ab91614c57..dca9f1e97bc 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,15 +1,15 @@ -'use strict'; import * as utils from 'src/utils'; -// import {config} from 'src/config'; import {registerBidder} from 'src/adapters/bidderFactory'; +import { VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'conversant'; -const URL = '//cmedia102.dev2.vcmedia.com/s2s/header/24'; +const URL = '//media.msg.dotomi.com/s2s/header/24'; const VERSION = '2.2.0'; export const spec = { code: BIDDER_CODE, aliases: ['cnvr'], // short code + supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -18,6 +18,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + // console.log('did we get called?', JSON.stringify(bid)); return !!(bid.params.site_id); }, /** @@ -37,16 +38,12 @@ export const spec = { const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - const tagId = utils.getBidIdParameter('tag_id', bid.params); - const pos = utils.getBidIdParameter('position', bid.params); siteId = utils.getBidIdParameter('site_id', bid.params); secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; requestId = bid.requestId; - const format = bid.sizes.map(function(d) { - return {w: d[0], h: d[1]}; - }); + const format = convertSizes(bid.sizes); const imp = { id: bid.bidId, @@ -56,20 +53,25 @@ export const spec = { displaymanaerver: VERSION }; - if (tagId != '') { - imp.tagid = tagId; - } + copyOptProperty(bid.params, 'tag_id', imp, 'tagid'); - const banner = { - format: format - }; + if (isVideoRequest(bid)) { + const video = {format: format}; - if (pos != '') { - banner.pos = pos; - } + copyOptProperty(bid.params, 'position', video, 'pos'); + copyOptProperty(bid.params, 'mimes', video); + copyOptProperty(bid.params, 'maxduration', video); + copyOptProperty(bid.params, 'protocols', video); + copyOptProperty(bid.params, 'api', video); + + imp.video = video; + } else { + const banner = {format: format}; - imp.banner = banner; + copyOptProperty(bid.params, 'position', banner, 'pos'); + imp.banner = banner; + } // console.log(JSON.stringify(imp)); return imp; @@ -93,6 +95,7 @@ export const spec = { method: 'POST', url: URL, data: payloadString, + payload: payload }; }, /** @@ -104,13 +107,46 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { let bidResponses = []; + const requestMap = {}; + + bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); // console.log('response', JSON.stringify(serverResponse)); // console.log('request', JSON.stringify(bidRequest)); + // console.log('map', JSON.stringify(requestMap)); if (serverResponse && serverResponse.id) { // console.log('processing serverResponse'); - serverResponse.seatbid.forEach(bidList => bidResponses = bidList.bid.reduce(parseBid, [])); + serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { + const responseCPM = parseFloat(conversantBid.price); + if (responseCPM > 0.0 && conversantBid.impid) { + const responseAd = conversantBid.adm || ''; + const responseNurl = conversantBid.nurl || ''; + const request = requestMap[conversantBid.impid]; + + const bid = { + requestId: conversantBid.impid, + bidderCode: BIDDER_CODE, + currency: 'USD', + cpm: responseCPM, + creativeId: conversantBid.crid || '' + }; + + if (request.video) { + bid.vastUrl = responseAd; + // bid.descriptionUrl = responseAd; + bid.mediaType = 'video'; + } else { + bid.ad = responseAd + ''; + } + + bid.width = conversantBid.w; + bid.height = conversantBid.h; + + // console.log('bid', JSON.stringify(bid)); + bidResponses.push(bid); + } + })); } // console.log('response', JSON.stringify(bidResponses)); @@ -143,30 +179,28 @@ function getDevice() { }; } -function parseBid(bidResponses, conversantBid) { - // console.log('parseBid called'); - const responseCPM = parseFloat(conversantBid.price); - if (responseCPM > 0.0 && conversantBid.impid) { - const responseAd = conversantBid.adm || ''; - const responseNurl = conversantBid.nurl || ''; - - const bid = { - requestId: conversantBid.impid, - bidderCode: BIDDER_CODE, - currency: 'USD', - cpm: responseCPM, - creativeId: conversantBid.crid || '' - }; +function convertSizes(bidSizes) { + let format; - bid.ad = responseAd + ''; - bid.width = conversantBid.w; - bid.height = conversantBid.h; - - // console.log('bid', JSON.stringify(bid)); - bidResponses.push(bid); + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + format = [{w: bidSizes[0], h: bidSizes[1]}]; + } else { + format = bidSizes.map(d => { return {w: d[0], h: d[1]}; }); } - return bidResponses; + return format; +} + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); +} + +function copyOptProperty(src, srcName, dst, dstName) { + dstName = dstName || srcName; + const obj = utils.getBidIdParameter(srcName, src); + if (obj !== '') { + dst[dstName] = obj; + } } registerBidder(spec); From b9c9c45e2c7f7f2965b216ffb3eb8e51cbaa7a50 Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 10 Oct 2017 15:26:53 -0700 Subject: [PATCH 08/14] Add tests and md --- modules/conversantBidAdapter.js | 50 +- modules/conversantBidAdapter.md | 41 ++ .../spec/modules/conversantBidAdapter_spec.js | 597 +++++++----------- 3 files changed, 300 insertions(+), 388 deletions(-) create mode 100644 modules/conversantBidAdapter.md diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index dca9f1e97bc..0c8ca1d0c1a 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -4,36 +4,33 @@ import { VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'conversant'; const URL = '//media.msg.dotomi.com/s2s/header/24'; +const SYNC_URL = '//media.msg.dotomi.com/w/user.sync'; const VERSION = '2.2.0'; export const spec = { code: BIDDER_CODE, - aliases: ['cnvr'], // short code + aliases: ['conversant'], // short code supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. * - * @param {BidRequest} - * bid The bid params to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - // console.log('did we get called?', JSON.stringify(bid)); - return !!(bid.params.site_id); + return !!(bid && bid.params && bid.params.site_id); }, /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - - * an array of bids + * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests) { - // console.log('hello world ', JSON.stringify(validBidRequests)); const loc = utils.getTopWindowLocation(); + const page = loc.pathname + loc.search + loc.hash; let siteId = ''; let secure = 0; - let page = loc.pathname + loc.search + loc.hash; let requestId = ''; const conversantImps = validBidRequests.map(function(bid) { @@ -72,7 +69,6 @@ export const spec = { imp.banner = banner; } - // console.log(JSON.stringify(imp)); return imp; }); @@ -88,9 +84,8 @@ export const spec = { device: getDevice(), at: 1 }; - const payloadString = JSON.stringify(payload); - // console.log('payload', payloadString); + const payloadString = JSON.stringify(payload); return { method: 'POST', url: URL, @@ -101,22 +96,17 @@ export const spec = { /** * Unpack the response from the server into a list of bids. * - * @param {*} - * serverResponse A successful response from the server. + * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - let bidResponses = []; + const bidResponses = []; const requestMap = {}; + const currency = serverResponse.cur || 'USD'; bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); - // console.log('response', JSON.stringify(serverResponse)); - // console.log('request', JSON.stringify(bidRequest)); - // console.log('map', JSON.stringify(requestMap)); - if (serverResponse && serverResponse.id) { - // console.log('processing serverResponse'); serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { const responseCPM = parseFloat(conversantBid.price); if (responseCPM > 0.0 && conversantBid.impid) { @@ -127,37 +117,37 @@ export const spec = { const bid = { requestId: conversantBid.impid, bidderCode: BIDDER_CODE, - currency: 'USD', + currency: currency, cpm: responseCPM, creativeId: conversantBid.crid || '' }; if (request.video) { bid.vastUrl = responseAd; - // bid.descriptionUrl = responseAd; bid.mediaType = 'video'; + + if (request.video.format.length >= 1) { + bid.width = request.video.format[0].w; + bid.height = request.video.format[0].h; + } } else { bid.ad = responseAd + ''; + bid.width = conversantBid.w; + bid.height = conversantBid.h; } - bid.width = conversantBid.w; - bid.height = conversantBid.h; - - // console.log('bid', JSON.stringify(bid)); bidResponses.push(bid); } })); } - // console.log('response', JSON.stringify(bidResponses)); - return bidResponses; }, getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { + if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: '//cmedia102.dev2.vcmedia.com/w/user.sync' + url: SYNC_URL }]; } } diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md new file mode 100644 index 00000000000..3d7f217bd7a --- /dev/null +++ b/modules/conversantBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +- Module Name: Conversant Bidder Adapter +- Module Type: Bidder Adapter +- Maintainer: pyang@conversantmedia.com + +# Description + +Module that connects to Conversant's demand sources. Supports banners and videos. + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: "conversant", + params: { + site_id: '108060' + } + }] + },{ + code: 'video-test-div', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: "conversant", + params: { + site_id: '88563', + api: [2], + protocols: [1, 2], + mimes: ['video/mp4'] + } + }] + }]; +``` \ No newline at end of file diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 57cd9411e66..704ecf67c15 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,376 +1,257 @@ -var expect = require('chai').expect; +import {expect} from 'chai'; +import {spec} from 'modules/conversantBidAdapter'; +import * as utils from 'src/utils'; + var Adapter = require('modules/conversantBidAdapter'); var bidManager = require('src/bidmanager'); -describe('Conversant adapter tests', function () { - var addBidResponseSpy; - var adapter; - - var bidderRequest = { - bidderCode: 'conversant', - bids: [ - { - bidId: 'bidId1', - bidder: 'conversant', - placementCode: 'div1', - sizes: [[300, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: 'tagid-1', - secure: false +describe('Conversant adapter tests', function() { + const siteId = '108060'; + + const bidRequests = [ + { + bidder: 'conversant', + params: { + site_id: siteId, + position: 1, + tag_id: 'tagid-1', + secure: false, + bidfloor: 0.5 + }, + placementCode: 'pcode000', + transactionId: 'tx000', + sizes: [[300, 250]], + bidId: 'bid000', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + secure: false + }, + placementCode: 'pcode001', + transactionId: 'tx001', + sizes: [[468, 60]], + bidId: 'bid001', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + position: 2, + tag_id: '', + secure: false + }, + placementCode: 'pcode002', + transactionId: 'tx002', + sizes: [[300, 600], [160, 600]], + bidId: 'bid002', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + api: [2], + protocols: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30 + }, + mediaTypes: { + video: { + context: 'instream' } + }, + placementCode: 'pcode003', + transactionId: 'tx003', + sizes: [640, 480], + bidId: 'bid003', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }]; + + const bidResponses = { + id: 'req000', + seatbid: [{ + bid: [{ + nurl: 'notify000', + adm: 'markup000', + crid: '1000', + impid: 'bid000', + price: 0.99, + w: 300, + h: 250, + adomain: ['https://example.com'], + id: 'bid000' }, { - bidId: 'bidId2', - bidder: 'conversant', - placementCode: 'div2', - sizes: [[300, 600]], - params: { - site_id: '87293', - secure: false - } + impid: 'bid001', + price: 0.00000, + id: 'bid001' }, { - bidId: 'bidId3', - bidder: 'conversant', - placementCode: 'div3', - sizes: [[300, 600], [160, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: '', - secure: false - } + nurl: 'notify002', + adm: 'markup002', + crid: '1002', + impid: 'bid002', + price: 2.99, + w: 300, + h: 600, + adomain: ['https://example.com'], + id: 'bid002' }, { - bidId: 'bidId4', - bidder: 'conversant', - placementCode: 'div4', - mediaType: 'video', - sizes: [[480, 480]], - params: { - site_id: '89192', - pos: 1, - tagid: 'tagid-4', - secure: false - } - } - ] + nurl: 'notify003', + adm: 'markup003', + crid: '1003', + impid: 'bid003', + price: 3.99, + adomain: ['https://example.com'], + id: 'bid003' + }] + }] }; - it('The Conversant response should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.conversantResponse).to.exist.and.to.be.a('function'); + it('Verify basic properties', function() { + expect(spec.code).to.equal('conversant'); + expect(spec.aliases).to.be.an('array').with.lengthOf(1); + expect(spec.aliases[0]).to.equal('conversant'); + expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('video'); }); - describe('Should submit bid responses correctly', function () { - beforeEach(function () { - addBidResponseSpy = sinon.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter = new Adapter(); - }); - - afterEach(function () { - addBidResponseSpy.restore(); - }); - - it('Should correctly submit valid and empty bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0 - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - // in this case, the valid bid (div2) is submitted before the empty bids (div1, div3) - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.22); - expect(firstBid.ad).to.equal('adm2' + ''); - expect(placementCode1).to.equal('div2'); - - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(placementCode2).to.equal('div1'); - - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit bids with statuses of 2 to the bid manager for empty bid responses', function () { - $$PREBID_GLOBAL$$.conversantResponse({id: 1, seatbid: []}); - - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - - expect(placementCode1).to.equal('div1'); - expect(firstBid.getStatusCode()).to.equal(2); - expect(firstBid.bidderCode).to.equal('conversant'); - - expect(placementCode2).to.equal('div2'); - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - - expect(placementCode3).to.equal('div3'); - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit valid bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 33333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; + it('Verify user syncs', function() { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({iframeEnabled: true})).to.be.undefined; + expect(spec.getUserSyncs({pixelEnabled: false})).to.be.undefined; - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.11); - expect(firstBid.ad).to.equal('adm' + ''); - expect(placementCode1).to.equal('div1'); - - expect(secondBid.getStatusCode()).to.equal(1); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(secondBid.cpm).to.equal(0.22); - expect(secondBid.ad).to.equal('adm2' + ''); - expect(placementCode2).to.equal('div2'); - - expect(thirdBid.getStatusCode()).to.equal(1); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(thirdBid.cpm).to.equal(0.33); - expect(thirdBid.ad).to.equal('adm3' + ''); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit video bid responses correctly.', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId4', - price: 0.11, - nurl: 'imp_tracker', - adm: 'vasturl' - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var videoBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode = addBidResponseSpy.getCall(0).args[0]; - - expect(videoBid.getStatusCode()).to.equal(1); - expect(videoBid.bidderCode).to.equal('conversant'); - expect(videoBid.cpm).to.equal(0.11); - expect(videoBid.vastUrl).to.equal('vasturl'); - expect(placementCode).to.equal('div4'); - }) + const syncs = spec.getUserSyncs({pixelEnabled: true}); + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('//media.msg.dotomi.com/w/user.sync'); }); - describe('Should submit the correct headers in the xhr', function () { - var server, - adapter; - - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); - - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); - - it('Should contain valid request header properties', function () { - adapter.callBids(bidderRequest); - server.respond(); - - var request = server.requests[0]; - expect(request.requestBody).to.not.be.empty; - }); + it('Verify isBidRequestValid', function() { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {site_id: ''}})).to.be.false; + expect(spec.isBidRequestValid({params: {site_id: '123'}})).to.be.true; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; }); - describe('Should create valid bid requests.', function () { - var server, - adapter; - - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); - - it('Should create valid bid requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[0].banner.format[0].w).to.equal(300); - expect(request.imp[0].banner.format[0].h).to.equal(600); - expect(request.imp[0].tagid).to.equal('tagid-1'); - expect(request.imp[0].banner.pos).to.equal(1); - expect(request.imp[0].secure).to.equal(0); - expect(request.site.id).to.equal('89192'); - }); - - it('Should not pass empty or missing optional parameters on requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); - - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[1].tagid).to.equal(undefined); - expect(request.imp[2].tagid).to.equal(undefined); - expect(request.imp[1].pos).to.equal(undefined); - }); - - it('Should create the format objects correctly.', function () { - adapter.callBids(bidderRequest); - server.respond(); + it('Verify buildRequest', function() { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('//media.msg.dotomi.com/s2s/header/24'); + const parsed = JSON.parse(request.data); + + expect(parsed).to.deep.equal(request.payload); + + expect(parsed).to.have.property('id', 'req000'); + expect(parsed).to.have.property('at', 1); + expect(parsed).to.have.property('imp'); + expect(parsed.imp).to.be.an('array').with.lengthOf(4); + + expect(parsed.imp[0]).to.have.property('id', 'bid000'); + expect(parsed.imp[0]).to.have.property('secure', 0); + expect(parsed.imp[0]).to.have.property('bidfloor', 0.5); + expect(parsed.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[0]).to.have.property('tagid', 'tagid-1'); + expect(parsed.imp[0]).to.have.property('banner'); + expect(parsed.imp[0].banner).to.have.property('pos', 1); + expect(parsed.imp[0].banner).to.have.property('format'); + expect(parsed.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + expect(parsed.imp[0]).to.not.have.property('video'); + + expect(parsed.imp[1]).to.have.property('id', 'bid001'); + expect(parsed.imp[1]).to.have.property('secure', 0); + expect(parsed.imp[1]).to.have.property('bidfloor', 0); + expect(parsed.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[1]).to.not.have.property('tagid'); + expect(parsed.imp[1]).to.have.property('banner'); + expect(parsed.imp[1].banner).to.not.have.property('pos'); + expect(parsed.imp[1].banner).to.have.property('format'); + expect(parsed.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); + + expect(parsed.imp[2]).to.have.property('id', 'bid002'); + expect(parsed.imp[2]).to.have.property('secure', 0); + expect(parsed.imp[2]).to.have.property('bidfloor', 0); + expect(parsed.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[2]).to.have.property('banner'); + expect(parsed.imp[2].banner).to.have.property('pos', 2); + expect(parsed.imp[2].banner).to.have.property('format'); + expect(parsed.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); + + expect(parsed.imp[3]).to.have.property('id', 'bid003'); + expect(parsed.imp[3]).to.have.property('secure', 0); + expect(parsed.imp[3]).to.have.property('bidfloor', 0); + expect(parsed.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(parsed.imp[3]).to.not.have.property('tagid'); + expect(parsed.imp[3]).to.have.property('video'); + expect(parsed.imp[3].video).to.not.have.property('pos'); + expect(parsed.imp[3].video).to.have.property('format'); + expect(parsed.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); + expect(parsed.imp[3].video).to.have.property('mimes'); + expect(parsed.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(parsed.imp[3].video).to.have.property('protocols'); + expect(parsed.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(parsed.imp[3].video).to.have.property('api'); + expect(parsed.imp[3].video.api).to.deep.equal([2]); + expect(parsed.imp[3].video).to.have.property('maxduration', 30); + expect(parsed.imp[3]).to.not.have.property('banner'); + + expect(parsed).to.have.property('site'); + expect(parsed.site).to.have.property('id', siteId); + expect(parsed.site).to.have.property('mobile').that.is.oneOf([0, 1]); + const loc = utils.getTopWindowLocation(); + const page = loc.pathname + loc.search + loc.hash; + expect(parsed.site).to.have.property('page', page); + + expect(parsed).to.have.property('device'); + expect(parsed.device).to.have.property('w', screen.width); + expect(parsed.device).to.have.property('h', screen.height); + expect(parsed.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(parsed.device).to.have.property('ua', navigator.userAgent); + }); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[2].banner.format.length).to.equal(2); - expect(request.imp[2].banner.format[0].w).to.equal(300); - expect(request.imp[2].banner.format[1].w).to.equal(160); - }); + it('Verify interpretResponse', function() { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.lengthOf(3); + + let bid = response[0]; + expect(bid).to.have.property('requestId', 'bid000'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 0.99); + expect(bid).to.have.property('creativeId', '1000'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 250); + expect(bid).to.have.property('ad', 'markup000'); + + // There is no bid001 because cpm is $0 + + bid = response[1]; + expect(bid).to.have.property('requestId', 'bid002'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 2.99); + expect(bid).to.have.property('creativeId', '1002'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 600); + expect(bid).to.have.property('ad', 'markup002'); + + bid = response[2]; + expect(bid).to.have.property('requestId', 'bid003'); + expect(bid).to.have.property('bidderCode', 'conversant'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 3.99); + expect(bid).to.have.property('creativeId', '1003'); + expect(bid).to.have.property('width', 640); + expect(bid).to.have.property('height', 480); + expect(bid).to.have.property('vastUrl', 'markup003'); + expect(bid).to.have.property('mediaType', 'video'); }); -}); +}) From 7e5796372c537af5704b5e2ad123594a38787556 Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 10 Oct 2017 15:28:07 -0700 Subject: [PATCH 09/14] Update conversant contact address --- modules/conversantBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index 3d7f217bd7a..1afdad6d544 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -2,7 +2,7 @@ - Module Name: Conversant Bidder Adapter - Module Type: Bidder Adapter -- Maintainer: pyang@conversantmedia.com +- Maintainer: mediapsr@conversantmedia.com # Description From f8f964393c01c986639b2dbb1275ac54eb1af10b Mon Sep 17 00:00:00 2001 From: pyang Date: Wed, 11 Oct 2017 10:44:40 -0700 Subject: [PATCH 10/14] Return data object in buildRequests without converting it to a string --- modules/conversantBidAdapter.js | 57 ++++++-- .../spec/modules/conversantBidAdapter_spec.js | 130 +++++++++--------- 2 files changed, 110 insertions(+), 77 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 0c8ca1d0c1a..37442440322 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -13,18 +13,18 @@ export const spec = { 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. + * + * @param {BidRequest} bid - The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { return !!(bid && bid.params && bid.params.site_id); }, /** * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(validBidRequests) { const loc = utils.getTopWindowLocation(); @@ -85,17 +85,15 @@ export const spec = { at: 1 }; - const payloadString = JSON.stringify(payload); return { method: 'POST', url: URL, - data: payloadString, - payload: payload + data: payload, }; }, /** * Unpack the response from the server into a list of bids. - * + * * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ @@ -104,7 +102,7 @@ export const spec = { const requestMap = {}; const currency = serverResponse.cur || 'USD'; - bidRequest.payload.imp.forEach(imp => requestMap[imp.id] = imp); + bidRequest.data.imp.forEach(imp => requestMap[imp.id] = imp); if (serverResponse && serverResponse.id) { serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { @@ -143,6 +141,12 @@ export const spec = { return bidResponses; }, + /** + * Return use sync info + * + * @param {SyncOptions} syncOptions - Info about usersyncs that the adapter should obey + * @return {UserSync} Adapter sync type and url + */ getUserSyncs: function(syncOptions) { if (syncOptions.pixelEnabled) { return [{ @@ -153,10 +157,20 @@ export const spec = { } } +/** + * Determine do-not-track state + * + * @returns {boolean} + */ function getDNT() { return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; } +/** + * Return openrtb device object that includes ua, width, and height. + * + * @returns {Device} Openrtb device object + */ function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { @@ -169,6 +183,13 @@ function getDevice() { }; } +/** + * Convert arrays of widths and heights to an array of objects with w and h properties. [[300, 250], + * [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] + * + * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights + * @returns {object[]} Array of objects with w and h + */ function convertSizes(bidSizes) { let format; @@ -181,10 +202,24 @@ function convertSizes(bidSizes) { return format; } +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ function isVideoRequest(bid) { return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); } +/** + * Copy property if exists from src to dst + * + * @param {object} src + * @param {string} srcName + * @param {object} dst + * @param {string} [dstName] - Optional. If not specified then srcName is used. + */ function copyOptProperty(src, srcName, dst, dstName) { dstName = dstName || srcName; const obj = utils.getBidIdParameter(srcName, src); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 704ecf67c15..d39bccf5006 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -145,75 +145,73 @@ describe('Conversant adapter tests', function() { const request = spec.buildRequests(bidRequests); expect(request.method).to.equal('POST'); expect(request.url).to.equal('//media.msg.dotomi.com/s2s/header/24'); - const parsed = JSON.parse(request.data); - - expect(parsed).to.deep.equal(request.payload); - - expect(parsed).to.have.property('id', 'req000'); - expect(parsed).to.have.property('at', 1); - expect(parsed).to.have.property('imp'); - expect(parsed.imp).to.be.an('array').with.lengthOf(4); - - expect(parsed.imp[0]).to.have.property('id', 'bid000'); - expect(parsed.imp[0]).to.have.property('secure', 0); - expect(parsed.imp[0]).to.have.property('bidfloor', 0.5); - expect(parsed.imp[0]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[0]).to.have.property('tagid', 'tagid-1'); - expect(parsed.imp[0]).to.have.property('banner'); - expect(parsed.imp[0].banner).to.have.property('pos', 1); - expect(parsed.imp[0].banner).to.have.property('format'); - expect(parsed.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - expect(parsed.imp[0]).to.not.have.property('video'); - - expect(parsed.imp[1]).to.have.property('id', 'bid001'); - expect(parsed.imp[1]).to.have.property('secure', 0); - expect(parsed.imp[1]).to.have.property('bidfloor', 0); - expect(parsed.imp[1]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[1]).to.not.have.property('tagid'); - expect(parsed.imp[1]).to.have.property('banner'); - expect(parsed.imp[1].banner).to.not.have.property('pos'); - expect(parsed.imp[1].banner).to.have.property('format'); - expect(parsed.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); - - expect(parsed.imp[2]).to.have.property('id', 'bid002'); - expect(parsed.imp[2]).to.have.property('secure', 0); - expect(parsed.imp[2]).to.have.property('bidfloor', 0); - expect(parsed.imp[2]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[2]).to.have.property('banner'); - expect(parsed.imp[2].banner).to.have.property('pos', 2); - expect(parsed.imp[2].banner).to.have.property('format'); - expect(parsed.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); - - expect(parsed.imp[3]).to.have.property('id', 'bid003'); - expect(parsed.imp[3]).to.have.property('secure', 0); - expect(parsed.imp[3]).to.have.property('bidfloor', 0); - expect(parsed.imp[3]).to.have.property('displaymanager', 'Prebid.js'); - expect(parsed.imp[3]).to.not.have.property('tagid'); - expect(parsed.imp[3]).to.have.property('video'); - expect(parsed.imp[3].video).to.not.have.property('pos'); - expect(parsed.imp[3].video).to.have.property('format'); - expect(parsed.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); - expect(parsed.imp[3].video).to.have.property('mimes'); - expect(parsed.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(parsed.imp[3].video).to.have.property('protocols'); - expect(parsed.imp[3].video.protocols).to.deep.equal([1, 2]); - expect(parsed.imp[3].video).to.have.property('api'); - expect(parsed.imp[3].video.api).to.deep.equal([2]); - expect(parsed.imp[3].video).to.have.property('maxduration', 30); - expect(parsed.imp[3]).to.not.have.property('banner'); - - expect(parsed).to.have.property('site'); - expect(parsed.site).to.have.property('id', siteId); - expect(parsed.site).to.have.property('mobile').that.is.oneOf([0, 1]); + const payload = request.data; + + expect(payload).to.have.property('id', 'req000'); + expect(payload).to.have.property('at', 1); + expect(payload).to.have.property('imp'); + expect(payload.imp).to.be.an('array').with.lengthOf(4); + + expect(payload.imp[0]).to.have.property('id', 'bid000'); + expect(payload.imp[0]).to.have.property('secure', 0); + expect(payload.imp[0]).to.have.property('bidfloor', 0.5); + expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); + expect(payload.imp[0]).to.have.property('banner'); + expect(payload.imp[0].banner).to.have.property('pos', 1); + expect(payload.imp[0].banner).to.have.property('format'); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + expect(payload.imp[0]).to.not.have.property('video'); + + expect(payload.imp[1]).to.have.property('id', 'bid001'); + expect(payload.imp[1]).to.have.property('secure', 0); + expect(payload.imp[1]).to.have.property('bidfloor', 0); + expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[1]).to.not.have.property('tagid'); + expect(payload.imp[1]).to.have.property('banner'); + expect(payload.imp[1].banner).to.not.have.property('pos'); + expect(payload.imp[1].banner).to.have.property('format'); + expect(payload.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); + + expect(payload.imp[2]).to.have.property('id', 'bid002'); + expect(payload.imp[2]).to.have.property('secure', 0); + expect(payload.imp[2]).to.have.property('bidfloor', 0); + expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[2]).to.have.property('banner'); + expect(payload.imp[2].banner).to.have.property('pos', 2); + expect(payload.imp[2].banner).to.have.property('format'); + expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); + + expect(payload.imp[3]).to.have.property('id', 'bid003'); + expect(payload.imp[3]).to.have.property('secure', 0); + expect(payload.imp[3]).to.have.property('bidfloor', 0); + expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[3]).to.not.have.property('tagid'); + expect(payload.imp[3]).to.have.property('video'); + expect(payload.imp[3].video).to.not.have.property('pos'); + expect(payload.imp[3].video).to.have.property('format'); + expect(payload.imp[3].video.format).to.deep.equal([{w: 640, h: 480}]); + expect(payload.imp[3].video).to.have.property('mimes'); + expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[3].video).to.have.property('protocols'); + expect(payload.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(payload.imp[3].video).to.have.property('api'); + expect(payload.imp[3].video.api).to.deep.equal([2]); + expect(payload.imp[3].video).to.have.property('maxduration', 30); + expect(payload.imp[3]).to.not.have.property('banner'); + + expect(payload).to.have.property('site'); + expect(payload.site).to.have.property('id', siteId); + expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); const loc = utils.getTopWindowLocation(); const page = loc.pathname + loc.search + loc.hash; - expect(parsed.site).to.have.property('page', page); + expect(payload.site).to.have.property('page', page); - expect(parsed).to.have.property('device'); - expect(parsed.device).to.have.property('w', screen.width); - expect(parsed.device).to.have.property('h', screen.height); - expect(parsed.device).to.have.property('dnt').that.is.oneOf([0, 1]); - expect(parsed.device).to.have.property('ua', navigator.userAgent); + expect(payload).to.have.property('device'); + expect(payload.device).to.have.property('w', screen.width); + expect(payload.device).to.have.property('h', screen.height); + expect(payload.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(payload.device).to.have.property('ua', navigator.userAgent); }); it('Verify interpretResponse', function() { From ccf4bbe62793222470836f8612cd94d128cc6a1c Mon Sep 17 00:00:00 2001 From: pyang Date: Mon, 16 Oct 2017 22:25:28 -0700 Subject: [PATCH 11/14] Better validation for site id --- modules/conversantBidAdapter.js | 47 ++++++++++++++----- .../spec/modules/conversantBidAdapter_spec.js | 11 +++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 37442440322..f1facf5af8f 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -11,33 +11,55 @@ export const spec = { code: BIDDER_CODE, aliases: ['conversant'], // short 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) { - return !!(bid && bid.params && bid.params.site_id); + if (!bid || !bid.params) { + utils.logWarn(BIDDER_CODE + ': Missing bid parameters'); + return false; + } + + if (!bid.params.site_id || !utils.isStr(bid.params.site_id)) { + utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string') + return false; + } + + if (isVideoRequest(bid)) { + if (!bid.params.mimes) { + // Give a warning but let it pass + utils.logWarn(BIDDER_CODE + ': mimes should be specified for videos'); + } else if (!utils.isArray(bid.params.mimes) || !bid.params.mimes.every(s => utils.isStr(s))) { + utils.logWarn(BIDDER_CODE + ': mimes must be an array of strings'); + return false; + } + } + + return true; }, + /** * Make a server request from the list of BidRequests. - * + * * @param {BidRequest[]} validBidRequests - an array of bids * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(validBidRequests) { const loc = utils.getTopWindowLocation(); const page = loc.pathname + loc.search + loc.hash; + const isPageSecure = (loc.protocol === 'https:') ? 1 : 0; let siteId = ''; - let secure = 0; let requestId = ''; const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const secure = isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0); siteId = utils.getBidIdParameter('site_id', bid.params); - secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; requestId = bid.requestId; const format = convertSizes(bid.sizes); @@ -93,7 +115,7 @@ export const spec = { }, /** * Unpack the response from the server into a list of bids. - * + * * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ @@ -141,9 +163,10 @@ export const spec = { return bidResponses; }, + /** * Return use sync info - * + * * @param {SyncOptions} syncOptions - Info about usersyncs that the adapter should obey * @return {UserSync} Adapter sync type and url */ @@ -159,7 +182,7 @@ export const spec = { /** * Determine do-not-track state - * + * * @returns {boolean} */ function getDNT() { @@ -168,7 +191,7 @@ function getDNT() { /** * Return openrtb device object that includes ua, width, and height. - * + * * @returns {Device} Openrtb device object */ function getDevice() { @@ -186,7 +209,7 @@ function getDevice() { /** * Convert arrays of widths and heights to an array of objects with w and h properties. [[300, 250], * [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] - * + * * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights * @returns {object[]} Array of objects with w and h */ @@ -204,7 +227,7 @@ function convertSizes(bidSizes) { /** * Check if it's a video bid request - * + * * @param {BidRequest} bid - Bid request generated from ad slots * @returns {boolean} True if it's a video bid */ @@ -214,7 +237,7 @@ function isVideoRequest(bid) { /** * Copy property if exists from src to dst - * + * * @param {object} src * @param {string} srcName * @param {object} dst diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index d39bccf5006..6d12951c663 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -139,6 +139,17 @@ describe('Conversant adapter tests', function() { expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; + + const simpleVideo = JSON.parse(JSON.stringify(bidRequests[3])); + simpleVideo.params.site_id = 123; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.site_id = siteId; + simpleVideo.params.mimes = [1, 2, 3]; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.mimes = 'bad type'; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + delete simpleVideo.params.mimes; + expect(spec.isBidRequestValid(simpleVideo)).to.be.true; }); it('Verify buildRequest', function() { From 6374f7a9fb7d9c7d1ccf934024d9146a47a1d408 Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 17 Oct 2017 13:17:30 -0700 Subject: [PATCH 12/14] Switch to use utils._each and utils._map --- modules/conversantBidAdapter.js | 81 ++++++++++--------- .../spec/modules/conversantBidAdapter_spec.js | 9 +++ 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index f1facf5af8f..46dcefe7472 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -55,7 +55,7 @@ export const spec = { let siteId = ''; let requestId = ''; - const conversantImps = validBidRequests.map(function(bid) { + const conversantImps = utils._map(validBidRequests, function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); const secure = isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0); @@ -124,41 +124,45 @@ export const spec = { const requestMap = {}; const currency = serverResponse.cur || 'USD'; - bidRequest.data.imp.forEach(imp => requestMap[imp.id] = imp); - - if (serverResponse && serverResponse.id) { - serverResponse.seatbid.forEach(bidList => bidList.bid.forEach(conversantBid => { - const responseCPM = parseFloat(conversantBid.price); - if (responseCPM > 0.0 && conversantBid.impid) { - const responseAd = conversantBid.adm || ''; - const responseNurl = conversantBid.nurl || ''; - const request = requestMap[conversantBid.impid]; - - const bid = { - requestId: conversantBid.impid, - bidderCode: BIDDER_CODE, - currency: currency, - cpm: responseCPM, - creativeId: conversantBid.crid || '' - }; - - if (request.video) { - bid.vastUrl = responseAd; - bid.mediaType = 'video'; - - if (request.video.format.length >= 1) { - bid.width = request.video.format[0].w; - bid.height = request.video.format[0].h; + if (bidRequest && bidRequest.data && bidRequest.data.imp) { + utils._each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); + } + + if (serverResponse && utils.isArray(serverResponse.seatbid)) { + utils._each(serverResponse.seatbid, function(bidList) { + utils._each(bidList.bid, function(conversantBid) { + const responseCPM = parseFloat(conversantBid.price); + if (responseCPM > 0.0 && conversantBid.impid) { + const responseAd = conversantBid.adm || ''; + const responseNurl = conversantBid.nurl || ''; + const request = requestMap[conversantBid.impid]; + + const bid = { + requestId: conversantBid.impid, + bidderCode: BIDDER_CODE, + currency: currency, + cpm: responseCPM, + creativeId: conversantBid.crid || '' + }; + + if (request.video) { + bid.vastUrl = responseAd; + bid.mediaType = 'video'; + + if (request.video.format.length >= 1) { + bid.width = request.video.format[0].w; + bid.height = request.video.format[0].h; + } + } else { + bid.ad = responseAd + ''; + bid.width = conversantBid.w; + bid.height = conversantBid.h; } - } else { - bid.ad = responseAd + ''; - bid.width = conversantBid.w; - bid.height = conversantBid.h; - } - bidResponses.push(bid); - } - })); + bidResponses.push(bid); + } + }) + }); } return bidResponses; @@ -178,7 +182,7 @@ export const spec = { }]; } } -} +}; /** * Determine do-not-track state @@ -207,8 +211,9 @@ function getDevice() { } /** - * Convert arrays of widths and heights to an array of objects with w and h properties. [[300, 250], - * [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] + * Convert arrays of widths and heights to an array of objects with w and h properties. + * + * [[300, 250], [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] * * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights * @returns {object[]} Array of objects with w and h @@ -219,7 +224,7 @@ function convertSizes(bidSizes) { if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { format = [{w: bidSizes[0], h: bidSizes[1]}]; } else { - format = bidSizes.map(d => { return {w: d[0], h: d[1]}; }); + format = utils._map(bidSizes, d => { return {w: d[0], h: d[1]}; }); } return format; diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 6d12951c663..0427ba38feb 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -263,4 +263,13 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('vastUrl', 'markup003'); expect(bid).to.have.property('mediaType', 'video'); }); + + it('Verify handling of bad responses', function() { + let response = spec.interpretResponse({}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + response = spec.interpretResponse({id: '123'}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + response = spec.interpretResponse({id: '123', seatbid: []}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + }); }) From 2696c49662206d889f252ef4a96a4e9b03711d9d Mon Sep 17 00:00:00 2001 From: pyang Date: Tue, 24 Oct 2017 12:16:08 -0700 Subject: [PATCH 13/14] Add tests for displaymanagerver --- modules/conversantBidAdapter.js | 2 +- test/spec/modules/conversantBidAdapter_spec.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 46dcefe7472..58ef87367e6 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -69,7 +69,7 @@ export const spec = { secure: secure, bidfloor: bidfloor || 0, displaymanager: 'Prebid.js', - displaymanaerver: VERSION + displaymanagerver: VERSION }; copyOptProperty(bid.params, 'tag_id', imp, 'tagid'); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 0427ba38feb..c2055f30ffa 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -167,6 +167,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[0]).to.have.property('secure', 0); expect(payload.imp[0]).to.have.property('bidfloor', 0.5); expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); expect(payload.imp[0]).to.have.property('banner'); expect(payload.imp[0].banner).to.have.property('pos', 1); @@ -178,6 +179,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[1]).to.have.property('secure', 0); expect(payload.imp[1]).to.have.property('bidfloor', 0); expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); expect(payload.imp[1]).to.not.have.property('tagid'); expect(payload.imp[1]).to.have.property('banner'); expect(payload.imp[1].banner).to.not.have.property('pos'); @@ -188,6 +190,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[2]).to.have.property('secure', 0); expect(payload.imp[2]).to.have.property('bidfloor', 0); expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); expect(payload.imp[2]).to.have.property('banner'); expect(payload.imp[2].banner).to.have.property('pos', 2); expect(payload.imp[2].banner).to.have.property('format'); @@ -197,6 +200,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[3]).to.have.property('secure', 0); expect(payload.imp[3]).to.have.property('bidfloor', 0); expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); expect(payload.imp[3]).to.not.have.property('tagid'); expect(payload.imp[3]).to.have.property('video'); expect(payload.imp[3].video).to.not.have.property('pos'); From 1c80cb99b689f0f033d5cdbaea05705aea0921bf Mon Sep 17 00:00:00 2001 From: pyang Date: Wed, 25 Oct 2017 15:53:42 -0700 Subject: [PATCH 14/14] Review changes for conversant --- modules/conversantBidAdapter.js | 31 ++++--- .../spec/modules/conversantBidAdapter_spec.js | 80 +++++++++---------- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 58ef87367e6..7e71d3be8aa 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -9,12 +9,12 @@ const VERSION = '2.2.0'; export const spec = { code: BIDDER_CODE, - aliases: ['conversant'], // short code + aliases: ['cnvr'], // short 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. */ @@ -24,7 +24,7 @@ export const spec = { return false; } - if (!bid.params.site_id || !utils.isStr(bid.params.site_id)) { + if (!utils.isStr(bid.params.site_id)) { utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string') return false; } @@ -44,7 +44,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. - * + * * @param {BidRequest[]} validBidRequests - an array of bids * @return {ServerRequest} Info describing the request to the server. */ @@ -55,7 +55,7 @@ export const spec = { let siteId = ''; let requestId = ''; - const conversantImps = utils._map(validBidRequests, function(bid) { + const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); const secure = isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0); @@ -115,14 +115,14 @@ export const spec = { }, /** * Unpack the response from the server into a list of bids. - * + * * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { const bidResponses = []; const requestMap = {}; - const currency = serverResponse.cur || 'USD'; + serverResponse = serverResponse.body; if (bidRequest && bidRequest.data && bidRequest.data.imp) { utils._each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); @@ -139,8 +139,7 @@ export const spec = { const bid = { requestId: conversantBid.impid, - bidderCode: BIDDER_CODE, - currency: currency, + currency: serverResponse.cur || 'USD', cpm: responseCPM, creativeId: conversantBid.crid || '' }; @@ -170,7 +169,7 @@ export const spec = { /** * Return use sync info - * + * * @param {SyncOptions} syncOptions - Info about usersyncs that the adapter should obey * @return {UserSync} Adapter sync type and url */ @@ -186,7 +185,7 @@ export const spec = { /** * Determine do-not-track state - * + * * @returns {boolean} */ function getDNT() { @@ -195,7 +194,7 @@ function getDNT() { /** * Return openrtb device object that includes ua, width, and height. - * + * * @returns {Device} Openrtb device object */ function getDevice() { @@ -212,9 +211,9 @@ function getDevice() { /** * Convert arrays of widths and heights to an array of objects with w and h properties. - * + * * [[300, 250], [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] - * + * * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights * @returns {object[]} Array of objects with w and h */ @@ -232,7 +231,7 @@ function convertSizes(bidSizes) { /** * Check if it's a video bid request - * + * * @param {BidRequest} bid - Bid request generated from ad slots * @returns {boolean} True if it's a video bid */ @@ -242,7 +241,7 @@ function isVideoRequest(bid) { /** * Copy property if exists from src to dst - * + * * @param {object} src * @param {string} srcName * @param {object} dst diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index c2055f30ffa..81da6867132 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -73,48 +73,50 @@ describe('Conversant adapter tests', function() { }]; const bidResponses = { - id: 'req000', - seatbid: [{ - bid: [{ - nurl: 'notify000', - adm: 'markup000', - crid: '1000', - impid: 'bid000', - price: 0.99, - w: 300, - h: 250, - adomain: ['https://example.com'], - id: 'bid000' - }, { - impid: 'bid001', - price: 0.00000, - id: 'bid001' - }, { - nurl: 'notify002', - adm: 'markup002', - crid: '1002', - impid: 'bid002', - price: 2.99, - w: 300, - h: 600, - adomain: ['https://example.com'], - id: 'bid002' - }, { - nurl: 'notify003', - adm: 'markup003', - crid: '1003', - impid: 'bid003', - price: 3.99, - adomain: ['https://example.com'], - id: 'bid003' + body: { + id: 'req000', + seatbid: [{ + bid: [{ + nurl: 'notify000', + adm: 'markup000', + crid: '1000', + impid: 'bid000', + price: 0.99, + w: 300, + h: 250, + adomain: ['https://example.com'], + id: 'bid000' + }, { + impid: 'bid001', + price: 0.00000, + id: 'bid001' + }, { + nurl: 'notify002', + adm: 'markup002', + crid: '1002', + impid: 'bid002', + price: 2.99, + w: 300, + h: 600, + adomain: ['https://example.com'], + id: 'bid002' + }, { + nurl: 'notify003', + adm: 'markup003', + crid: '1003', + impid: 'bid003', + price: 3.99, + adomain: ['https://example.com'], + id: 'bid003' + }] }] - }] - }; + }, + headers: {}}; it('Verify basic properties', function() { expect(spec.code).to.equal('conversant'); expect(spec.aliases).to.be.an('array').with.lengthOf(1); - expect(spec.aliases[0]).to.equal('conversant'); + expect(spec.aliases[0]).to.equal('cnvr'); expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(1); expect(spec.supportedMediaTypes[0]).to.equal('video'); }); @@ -133,7 +135,6 @@ describe('Conversant adapter tests', function() { it('Verify isBidRequestValid', function() { expect(spec.isBidRequestValid({})).to.be.false; expect(spec.isBidRequestValid({params: {}})).to.be.false; - expect(spec.isBidRequestValid({params: {site_id: ''}})).to.be.false; expect(spec.isBidRequestValid({params: {site_id: '123'}})).to.be.true; expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; @@ -236,7 +237,6 @@ describe('Conversant adapter tests', function() { let bid = response[0]; expect(bid).to.have.property('requestId', 'bid000'); - expect(bid).to.have.property('bidderCode', 'conversant'); expect(bid).to.have.property('currency', 'USD'); expect(bid).to.have.property('cpm', 0.99); expect(bid).to.have.property('creativeId', '1000'); @@ -248,7 +248,6 @@ describe('Conversant adapter tests', function() { bid = response[1]; expect(bid).to.have.property('requestId', 'bid002'); - expect(bid).to.have.property('bidderCode', 'conversant'); expect(bid).to.have.property('currency', 'USD'); expect(bid).to.have.property('cpm', 2.99); expect(bid).to.have.property('creativeId', '1002'); @@ -258,7 +257,6 @@ describe('Conversant adapter tests', function() { bid = response[2]; expect(bid).to.have.property('requestId', 'bid003'); - expect(bid).to.have.property('bidderCode', 'conversant'); expect(bid).to.have.property('currency', 'USD'); expect(bid).to.have.property('cpm', 3.99); expect(bid).to.have.property('creativeId', '1003');