diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index d51008559f2..7e71d3be8aa 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,99 +1,70 @@ -'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'); +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +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 + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid - The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (!bid || !bid.params) { + utils.logWarn(BIDDER_CODE + ': Missing bid parameters'); + return false; + } -/** - * 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); + if (!utils.isStr(bid.params.site_id)) { + utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string') + return false; } - }; - var getDNT = function () { - return n.doNotTrack === '1' || w.doNotTrack === '1' || n.msDoNotTrack === '1' || n.doNotTrack === 'yes'; - }; + 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; + } + } - 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 - }; - }; + return true; + }, - var callBids = function (params) { - var conversantBids = params.bids || []; - requestBids(conversantBids); - }; + /** + * 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 requestId = ''; - 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; - - 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; - - 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 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); + requestId = bid.requestId; + + const format = convertSizes(bid.sizes); - imp = { + const imp = { id: bid.bidId, secure: secure, bidfloor: bidfloor || 0, @@ -101,178 +72,187 @@ var ConversantAdapter = function () { displaymanagerver: VERSION }; - 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; - } + copyOptProperty(bid.params, 'tag_id', imp, 'tagid'); - maxduration = utils.getBidIdParameter('maxduration', bid.params); - if (maxduration !== '') { - video.maxduration = maxduration; - } + if (isVideoRequest(bid)) { + const video = {format: format}; - protocols = utils.getBidIdParameter('protocols', bid.params); - if (protocols !== '') { - video.protocols = protocols; - } - - api = utils.getBidIdParameter('api', bid.params); - if (api !== '') { - video.api = api; - } - - if (pos !== '') { - video.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 { - var banner = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; + const banner = {format: format}; + + copyOptProperty(bid.params, 'position', banner, 'pos'); - if (pos !== '') { - banner.pos = pos; - } imp.banner = banner; } - 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 }; - var url = secure ? 'https:' + conversantUrl : location.protocol + conversantUrl; - ajax(url, appendScript, JSON.stringify(conversantBidReqs), { - withCredentials: true - }); - }; + return { + method: 'POST', + url: URL, + 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. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const requestMap = {}; + serverResponse = serverResponse.body; + + if (bidRequest && bidRequest.data && bidRequest.data.imp) { + utils._each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); + } - 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); - } + 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, + currency: serverResponse.cur || 'USD', + 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; + } + + bidResponses.push(bid); + } + }) }); } - }; - 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 || ''; - - // 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 + ''; - } + 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 [{ + type: 'image', + url: SYNC_URL + }]; + } + } +}; - 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]; - } +/** + * Determine do-not-track state + * + * @returns {boolean} + */ +function getDNT() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} - bidmanager.addBidResponse(placementCode, bid); - } - } - }); - addEmptyBidResponses(placementsWithBidsBack); +/** + * Return openrtb device object that includes ua, width, and height. + * + * @returns {Device} Openrtb device object + */ +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; + return { + h: screen.height, + w: screen.width, + dnt: getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent }; +} - // 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([]); - } - // 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 +/** + * 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; - return { - callBids: callBids - }; -}; + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + format = [{w: bidSizes[0], h: bidSizes[1]}]; + } else { + format = utils._map(bidSizes, d => { return {w: d[0], h: d[1]}; }); + } -adaptermanager.registerBidAdapter(new ConversantAdapter(), 'conversant', { - supportedMediaTypes: ['video'] -}); + return format; +} -module.exports = ConversantAdapter; +/** + * 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); + if (obj !== '') { + dst[dstName] = obj; + } +} + +registerBidder(spec); diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md new file mode 100644 index 00000000000..1afdad6d544 --- /dev/null +++ b/modules/conversantBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +- Module Name: Conversant Bidder Adapter +- Module Type: Bidder Adapter +- Maintainer: mediapsr@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..81da6867132 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,376 +1,277 @@ -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 - } - }, { - bidId: 'bidId2', - bidder: 'conversant', - placementCode: 'div2', - sizes: [[300, 600]], - params: { - site_id: '87293', - secure: false - } - }, { - bidId: 'bidId3', - bidder: 'conversant', - placementCode: 'div3', - sizes: [[300, 600], [160, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: '', - 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' } - }, { - bidId: 'bidId4', - bidder: 'conversant', - placementCode: 'div4', - mediaType: 'video', - sizes: [[480, 480]], - params: { - site_id: '89192', - pos: 1, - tagid: 'tagid-4', - secure: false - } - } - ] - }; - - it('The Conversant response should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.conversantResponse).to.exist.and.to.be.a('function'); - }); - - 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]; - - 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'); - }) - }); - - describe('Should submit the correct headers in the xhr', function () { - var server, - adapter; - - var bidResponse = { - id: 123, + }, + placementCode: 'pcode003', + transactionId: 'tx003', + sizes: [640, 480], + bidId: 'bid003', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }]; + + const bidResponses = { + body: { + id: 'req000', seatbid: [{ bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, + nurl: 'notify000', + adm: 'markup000', + crid: '1000', + impid: 'bid000', + price: 0.99, w: 300, - ext: {} + h: 250, + adomain: ['https://example.com'], + id: 'bid000' }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 + impid: 'bid001', + price: 0.00000, + id: 'bid001' }, { - 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; - }); - }); - 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, + nurl: 'notify002', + adm: 'markup002', + crid: '1002', + impid: 'bid002', + price: 2.99, w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 + h: 600, + adomain: ['https://example.com'], + id: 'bid002' }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 + nurl: 'notify003', + adm: 'markup003', + crid: '1003', + impid: 'bid003', + price: 3.99, + adomain: ['https://example.com'], + id: 'bid003' }] }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); + }, + 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('cnvr'); + expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('video'); + }); - 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('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; - 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'); - }); + 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'); + }); - it('Should not pass empty or missing optional parameters on requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); + it('Verify isBidRequestValid', function() { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).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; + + 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; + }); - 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('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 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('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); + 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[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'); + 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[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'); + 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[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'); + 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(payload.site).to.have.property('page', page); + + 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('Should create the format objects correctly.', function () { - adapter.callBids(bidderRequest); - server.respond(); + 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('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('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('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'); + }); - 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 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); }); -}); +})