From 89a8ed6cfc509a4020dafe8d625ea021884e6d47 Mon Sep 17 00:00:00 2001 From: Dan Harton Date: Wed, 18 Oct 2017 11:26:58 -0700 Subject: [PATCH 01/62] Update AdButler adapter for Prebid v1.0 (#1664) * Adding AdButler Adapter * Prevent AdButler TypeError Only attempt to build a bid response if we have the information of which bid to respond to. * Refactor AdButler Testing Now stubbing adLoader instead of spying. Additional changes to ensure all tests still passed. * Prevent AdButler TypeErrors Prevent AdButler TypeErrors and pass bid request object into the bid response. * Add optional domain parameter. Add optional domain parameter to AdButler adapter. * Update AdButler adapter to Prebid 1.0 * Code Style updates based on lint warnings. * Removed mutable global, simplified tests, and added markdown file. * Update c1x adapter tests & remove old adbutler_spec file. --- modules/adbutlerBidAdapter.js | 229 +++---- modules/adbutlerBidAdapter.md | 31 + test/spec/modules/adbutlerBidAdapter_spec.js | 685 +++++-------------- test/spec/modules/c1xBidAdapter_spec.js | 17 + 4 files changed, 343 insertions(+), 619 deletions(-) create mode 100644 modules/adbutlerBidAdapter.md diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js index d6492a72e1c..f633eba98a3 100644 --- a/modules/adbutlerBidAdapter.js +++ b/modules/adbutlerBidAdapter.js @@ -1,130 +1,122 @@ -/** - * @overview AdButler Prebid.js adapter. - * @author dkharton - */ - 'use strict'; -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var adaptermanager = require('src/adaptermanager'); - -var AdButlerAdapter = function AdButlerAdapter() { - function _callBids(params) { - var bids = params.bids || []; - var callbackData = {}; - var zoneCount = {}; - var pageID = Math.floor(Math.random() * 10e6); - - // Build and send bid requests - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - var zoneID = utils.getBidIdParameter('zoneID', bid.params); - var callbackID; - - if (!(zoneID in zoneCount)) { - zoneCount[zoneID] = 0; +import * as utils from 'src/utils'; +import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'adbutler'; + +export const spec = { + code: BIDDER_CODE, + pageID: Math.floor(Math.random() * 10e6), + + isBidRequestValid: function (bid) { + return !!(bid.params.accountID && bid.params.zoneID); + }, + + buildRequests: function (validBidRequests) { + var i; + var zoneID; + var bidRequest; + var accountID; + var keyword; + var domain; + var requestURI; + var serverRequests = []; + var zoneCounters = {}; + + for (i = 0; i < validBidRequests.length; i++) { + bidRequest = validBidRequests[i]; + zoneID = utils.getBidIdParameter('zoneID', bidRequest.params); + accountID = utils.getBidIdParameter('accountID', bidRequest.params); + keyword = utils.getBidIdParameter('keyword', bidRequest.params); + domain = utils.getBidIdParameter('domain', bidRequest.params); + + if (!(zoneID in zoneCounters)) { + zoneCounters[zoneID] = 0; } - // build callbackID to get placementCode later - callbackID = zoneID + '_' + zoneCount[zoneID]; + if (typeof domain === 'undefined' || domain.length === 0) { + domain = 'servedbyadbutler.com'; + } - callbackData[callbackID] = {}; - callbackData[callbackID].bidId = bid.bidId; + requestURI = location.protocol + '//' + domain + '/adserve/;type=hbr;'; + requestURI += 'ID=' + encodeURIComponent(accountID) + ';'; + requestURI += 'setID=' + encodeURIComponent(zoneID) + ';'; + requestURI += 'pid=' + encodeURIComponent(spec.pageID) + ';'; + requestURI += 'place=' + encodeURIComponent(zoneCounters[zoneID]) + ';'; - var adRequest = buildRequest(bid, zoneCount[zoneID], pageID); - zoneCount[zoneID]++; + // append the keyword for targeting if one was passed in + if (keyword !== '') { + requestURI += 'kw=' + encodeURIComponent(keyword) + ';'; + } - adloader.loadScript(adRequest); + zoneCounters[zoneID]++; + serverRequests.push({ + method: 'GET', + url: requestURI, + data: {}, + bidRequest: bidRequest + }); } + return serverRequests; + }, + + interpretResponse: function (serverResponse, bidRequest) { + var bidObj = bidRequest.bidRequest; + var bidResponses = []; + var bidResponse = {}; + var isCorrectSize = false; + var isCorrectCPM = true; + var CPM; + var minCPM; + var maxCPM; + var width; + var height; + + if (serverResponse && serverResponse.status === 'SUCCESS' && bidObj) { + CPM = serverResponse.cpm; + minCPM = utils.getBidIdParameter('minCPM', bidObj.params); + maxCPM = utils.getBidIdParameter('maxCPM', bidObj.params); + width = parseInt(serverResponse.width); + height = parseInt(serverResponse.height); + + // Ensure response CPM is within the given bounds + if (minCPM !== '' && CPM < parseFloat(minCPM)) { + isCorrectCPM = false; + } + if (maxCPM !== '' && CPM > parseFloat(maxCPM)) { + isCorrectCPM = false; + } - // Define callback function for bid responses - $$PREBID_GLOBAL$$.adbutlerCB = function(aBResponseObject) { - var bidResponse = {}; - var callbackID = aBResponseObject.zone_id + '_' + aBResponseObject.place; - var width = parseInt(aBResponseObject.width); - var height = parseInt(aBResponseObject.height); - var isCorrectSize = false; - var isCorrectCPM = true; - var CPM; - var minCPM; - var maxCPM; - var bidObj = callbackData[callbackID] ? utils.getBidRequest(callbackData[callbackID].bidId) : null; - - if (bidObj) { - if (aBResponseObject.status === 'SUCCESS') { - CPM = aBResponseObject.cpm; - minCPM = utils.getBidIdParameter('minCPM', bidObj.params); - maxCPM = utils.getBidIdParameter('maxCPM', bidObj.params); - - // Ensure response CPM is within the given bounds - if (minCPM !== '' && CPM < parseFloat(minCPM)) { - isCorrectCPM = false; - } - if (maxCPM !== '' && CPM > parseFloat(maxCPM)) { - isCorrectCPM = false; - } - - // Ensure that response ad matches one of the placement sizes. - utils._each(bidObj.sizes, function(size) { - if (width === size[0] && height === size[1]) { - isCorrectSize = true; - } - }); - - if (isCorrectCPM && isCorrectSize) { - bidResponse = bidfactory.createBid(1, bidObj); - bidResponse.bidderCode = 'adbutler'; - bidResponse.cpm = CPM; - bidResponse.width = width; - bidResponse.height = height; - bidResponse.ad = aBResponseObject.ad_code; - bidResponse.ad += addTrackingPixels(aBResponseObject.tracking_pixels); - } else { - bidResponse = bidfactory.createBid(2, bidObj); - bidResponse.bidderCode = 'adbutler'; - } - } else { - bidResponse = bidfactory.createBid(2, bidObj); - bidResponse.bidderCode = 'adbutler'; + // Ensure that response ad matches one of the placement sizes. + utils._each(bidObj.sizes, function (size) { + if (width === size[0] && height === size[1]) { + isCorrectSize = true; } - - bidmanager.addBidResponse(bidObj.placementCode, bidResponse); + }); + if (isCorrectCPM && isCorrectSize) { + bidResponse.requestId = bidObj.bidId; + bidResponse.bidderCode = spec.code; + bidResponse.creativeId = serverResponse.placement_id; + bidResponse.cpm = CPM; + bidResponse.width = width; + bidResponse.height = height; + bidResponse.ad = serverResponse.ad_code; + bidResponse.ad += spec.addTrackingPixels(serverResponse.tracking_pixels); + bidResponse.currency = 'USD'; + bidResponse.netRevenue = true; + bidResponse.ttl = config.getConfig('_bidderTimeout'); + bidResponse.referrer = utils.getTopWindowUrl(); + bidResponses.push(bidResponse); } - }; - } - - function buildRequest(bid, adIndex, pageID) { - var accountID = utils.getBidIdParameter('accountID', bid.params); - var zoneID = utils.getBidIdParameter('zoneID', bid.params); - var keyword = utils.getBidIdParameter('keyword', bid.params); - var domain = utils.getBidIdParameter('domain', bid.params); - - if (typeof domain === 'undefined' || domain.length === 0) { - domain = 'servedbyadbutler.com'; } + return bidResponses; + }, - var requestURI = location.protocol + '//' + domain + '/adserve/;type=hbr;'; - requestURI += 'ID=' + encodeURIComponent(accountID) + ';'; - requestURI += 'setID=' + encodeURIComponent(zoneID) + ';'; - requestURI += 'pid=' + encodeURIComponent(pageID) + ';'; - requestURI += 'place=' + encodeURIComponent(adIndex) + ';'; - - // append the keyword for targeting if one was passed in - if (keyword !== '') { - requestURI += 'kw=' + encodeURIComponent(keyword) + ';'; - } - requestURI += 'jsonpfunc=$$PREBID_GLOBAL$$.adbutlerCB;'; - requestURI += 'click=CLICK_MACRO_PLACEHOLDER'; - - return requestURI; - } - - function addTrackingPixels(trackingPixels) { + addTrackingPixels: function (trackingPixels) { var trackingPixelMarkup = ''; - utils._each(trackingPixels, function(pixelURL) { + utils._each(trackingPixels, function (pixelURL) { var trackingPixel = ''; @@ -133,14 +125,5 @@ var AdButlerAdapter = function AdButlerAdapter() { }); return trackingPixelMarkup; } - - // Export the callBids function, so that prebid.js can execute this function - // when the page asks to send out bid requests. - return { - callBids: _callBids - }; }; - -adaptermanager.registerBidAdapter(new AdButlerAdapter(), 'adbutler'); - -module.exports = AdButlerAdapter; +registerBidder(spec); diff --git a/modules/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md new file mode 100644 index 00000000000..5905074270a --- /dev/null +++ b/modules/adbutlerBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +**Module Name**: AdButler Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: dan@sparklit.com + +# Description + +Module that connects to an AdButler zone to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'display-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "adbutler", + params: { + accountID: '167283', + zoneID: '210093', + keyword: 'red', //optional + minCPM: '1.00', //optional + maxCPM: '5.00' //optional + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js index d026ac8de98..352358be8d0 100644 --- a/test/spec/modules/adbutlerBidAdapter_spec.js +++ b/test/spec/modules/adbutlerBidAdapter_spec.js @@ -1,516 +1,209 @@ -describe('adbutler adapter tests', function () { - var expect = require('chai').expect; - var adapter = require('modules/adbutlerBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); +import {expect} from 'chai'; +import {spec} from 'modules/adbutlerBidAdapter'; - describe('creation of bid url', function () { - var stubLoadScript; +describe('AdButler adapter', () => { + let bidRequests; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - it('should be called', function () { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - adapter().callBids(params); - - sinon.assert.called(stubLoadScript); - }); - - it('should populate the keyword', function() { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - keyword: 'fish' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - adapter().callBids(params); - - var requestURI = stubLoadScript.getCall(0).args[0]; - - expect(requestURI).to.have.string(';kw=fish;'); - }); - - it('should use custom domain string', function() { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '107878', - zoneID: '86133', - domain: 'servedbyadbutler.com.dan.test' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - ] - }; - - adapter().callBids(params); - - var requestURI = stubLoadScript.getCall(0).args[0]; - - expect(requestURI).to.have.string('.dan.test'); - }); - }); - describe('bid responses', function() { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', + beforeEach(() => { + bidRequests = [ + { bidder: 'adbutler', - bids: [ - { - bidId: '3c94018cdbf2f68-1', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); + params: { + accountID: '167283', + zoneID: '210093', + keyword: 'red', + minCPM: '1.00', + maxCPM: '5.00' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/123456/header-bid-tag-1'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('adbutler'); - expect(bidObject1.cpm).to.equal(1.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-2', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210085', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'NO_ELIGIBLE_ADS', - zone_id: 210085, - width: 728, - height: 90, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/123456/header-bid-tag-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('adbutler'); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response on incorrect size', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-3', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210085', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210085, - cpm: 1.5, - width: 728, - height: 90, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response with CPM too low', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-4', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - minCPM: '5.00' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response with CPM too high', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-5', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - maxCPM: '1.00' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); + ]; }); - describe('ad code', function() { - it('should be populated', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-6', - sizes: [[300, 250]], + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + let validBid = { bidder: 'adbutler', params: { accountID: '167283', zoneID: '210093' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0, - ad_code: '' - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); + } + }, + isValid = spec.isBidRequestValid(validBid); - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.ad).to.have.length.above(1); - - stubAddBidResponse.restore(); - }); + expect(isValid).to.equal(true); + }); - it('should contain tracking pixels', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-7', - sizes: [[300, 250]], + it('should reject invalid bid', () => { + let invalidBid = { bidder: 'adbutler', params: { accountID: '167283', - zoneID: '210093' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should use custom domain string', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + }, + requestId: '10b327aa396609', + placementCode: '/123456/header-bid-tag-1' + } + ], + requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string('.dan.test'); + }); + + it('should set default domain', () => { + let requests = spec.buildRequests(bidRequests), + request = requests[0]; + + let [domain] = request.url.split('/adserve/'); + + expect(domain).to.equal('http://servedbyadbutler.com'); + }); + + it('should set the keyword parameter', () => { + let requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string(';kw=red;'); + }); + + it('should increment the count for the same zone', () => { + let bidRequests = [ + { + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + } + }, { + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + } }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0, - ad_code: '', - tracking_pixels: [ - 'http://tracking.pixel.com/params=info' - ] - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.ad).to.have.string('http://tracking.pixel.com/params=info'); + ], + requests = spec.buildRequests(bidRequests), + firstRequest = requests[0].url, + secondRequest = requests[1].url; + + expect(firstRequest).to.have.string(';place=0;'); + expect(secondRequest).to.have.string(';place=1;'); + }); + }); - stubAddBidResponse.restore(); + describe('bid responses', () => { + it('should return complete bid response', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 1.5, + width: 300, + height: 250, + place: 0, + ad_code: '', + tracking_pixels: [ + 'http://tracking.pixel.com/params=info' + ] + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].bidderCode).to.equal('adbutler'); + expect(bids[0].cpm).to.equal(1.5); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].ad).to.have.string('http://tracking.pixel.com/params=info'); + }); + + it('should return empty bid response', () => { + let serverResponse = { + status: 'NO_ELIGIBLE_ADS', + zone_id: 210083, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210083, + cpm: 1.5, + width: 728, + height: 90, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too low', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 0.75, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too high', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 7.00, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); }); }); }); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js index 3e482dfaae9..e1a48a5b701 100644 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -177,6 +177,23 @@ describe('c1x adapter tests: ', () => { }); it('should show error when bidder sends invalid bid responses', () => { let responses; + let adUnits = []; + let unit = {}; + let params = getDefaultBidRequest(); + + unit.bids = params.bids; + unit.code = '/123456/header-bid-tag-1'; + unit.sizes = [[300, 250]]; + adUnits.push(unit); + + if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { + $$PREBID_GLOBAL$$._bidsRequested = [params]; + } else { + $$PREBID_GLOBAL$$._bidsRequested.push(params); + } + + $$PREBID_GLOBAL$$.adUnits = adUnits; + pbjs._c1xResponse(responses); let bidObject = stubAddBidResponse.getCall(0).args[1]; expect(bidObject.statusMessage).to.equal('Bid returned empty or error response'); From bd883523f02186336d64c4486018510dd8bba810 Mon Sep 17 00:00:00 2001 From: John Salis Date: Wed, 18 Oct 2017 18:54:30 -0400 Subject: [PATCH 02/62] Update Beachfront adapter for v1.0 (#1675) * Update Beachfront adapter for v1.0 * Revert Beachfront test endpoint * Add Beachfront markdown file * Add mediaTypes to example * Fix formatting * Remove descriptionUrl from bid response --- modules/beachfrontBidAdapter.js | 201 ++++++---------- modules/beachfrontBidAdapter.md | 35 +++ .../spec/modules/beachfrontBidAdapter_spec.js | 220 ++++++++++-------- 3 files changed, 231 insertions(+), 225 deletions(-) create mode 100644 modules/beachfrontBidAdapter.md diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 0193df6a3ac..2729dcdd324 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,135 +1,88 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; - -const ENDPOINT = '//reachms.bfmio.com/bid.json?exchange_id='; - -function BeachfrontAdapter() { - var baseAdapter = new Adapter('beachfront'); - - baseAdapter.callBids = function (bidRequests) { - const bids = bidRequests.bids || []; - bids.forEach(function(bid) { - var bidRequest = getBidRequest(bid); - var RTBDataParams = prepareAndSaveRTBRequestParams(bid); - if (!RTBDataParams) { - var error = 'No bid params'; - utils.logError(error); - if (bid && bid.placementCode) { - bidmanager.addBidResponse(bid.placementCode, createBid(bid, STATUS.NO_BID)); - } - return; - } - var BID_URL = ENDPOINT + RTBDataParams.appId; - ajax(BID_URL, handleResponse(bidRequest), JSON.stringify(RTBDataParams), { - contentType: 'text/plain', - withCredentials: true - }); +import { registerBidder } from 'src/adapters/bidderFactory'; + +export const ENDPOINT = '//reachms.bfmio.com/bid.json?exchange_id='; + +export const spec = { + code: 'beachfront', + supportedMediaTypes: ['video'], + + isBidRequestValid(bid) { + return !!(bid && bid.params && bid.params.appId && bid.params.bidfloor); + }, + + buildRequests(bids) { + return bids.map(bid => { + return { + method: 'POST', + url: ENDPOINT + bid.params.appId, + data: createRequestParams(bid), + bidRequest: bid + }; }); - }; - - function getBidRequest(bid) { - if (!bid || !bid.params || !bid.params.appId) { - return; - } - - var bidRequest = bid; - bidRequest.width = parseInt(bid.sizes[0], 10) || undefined; - bidRequest.height = parseInt(bid.sizes[1], 10) || undefined; - return bidRequest; - } - - function prepareAndSaveRTBRequestParams(bid) { - if (!bid || !bid.params || !bid.params.appId || !bid.params.bidfloor) { - return; - } + }, - function fetchDeviceType() { - return ((/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent) ? 1 : ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent) ? 1 : 2)); + interpretResponse(response, { bidRequest }) { + if (!response || !response.url || !response.bidPrice) { + utils.logWarn(`No valid bids from ${spec.code} bidder`); + return []; } - - var bidRequestObject = { - isPrebid: true, - appId: bid.params.appId, - domain: document.location.hostname, - imp: [{ - video: { - w: bid.width, - h: bid.height - }, - bidfloor: bid.params.bidfloor - }], - site: { - page: utils.getTopWindowLocation().host - }, - device: { - ua: navigator.userAgent, - devicetype: fetchDeviceType() - }, - cur: ['USD'] - }; - return bidRequestObject; - } - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(bidRequest) { - return function(response) { - var parsed; - if (response) { - try { - parsed = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } else { - utils.logWarn('No bid response'); - } - - if (!parsed || parsed.error || !parsed.url || !parsed.bidPrice) { - utils.logWarn('No Valid Bid'); - bidmanager.addBidResponse(bidRequest.placementCode, createBid(bidRequest, STATUS.NO_BID)); - return; - } - - var newBid = {}; - newBid.price = parsed.bidPrice; - newBid.url = parsed.url; - newBid.bidId = bidRequest.bidId; - bidmanager.addBidResponse(bidRequest.placementCode, createBid(bidRequest, STATUS.GOOD, newBid)); + let size = getSize(bidRequest.sizes); + return { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: response.bidPrice, + creativeId: response.cmpId, + vastUrl: response.url, + width: size.width, + height: size.height, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true }; } +}; + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; + return { + width: parseInt(width, 10) || undefined, + height: parseInt(height, 10) || undefined + }; +} - function createBid(bidRequest, status, tag) { - var bid = bidfactory.createBid(status, tag); - bid.code = baseAdapter.getBidderCode(); - bid.bidderCode = bidRequest.bidder; - if (!tag || status !== STATUS.GOOD) { - return bid; - } - - bid.cpm = tag.price; - bid.creative_id = tag.cmpId; - bid.width = bidRequest.width; - bid.height = bidRequest.height; - bid.descriptionUrl = tag.url; - bid.vastUrl = tag.url; - bid.mediaType = 'video'; - - return bid; - } +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent); +} - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode - }); +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent); } -adaptermanager.registerBidAdapter(new BeachfrontAdapter(), 'beachfront', { - supportedMediaTypes: ['video'] -}); +function createRequestParams(bid) { + let size = getSize(bid.sizes); + return { + isPrebid: true, + appId: bid.params.appId, + domain: document.location.hostname, + imp: [{ + video: { + w: size.width, + h: size.height + }, + bidfloor: bid.params.bidfloor + }], + site: { + page: utils.getTopWindowLocation().host + }, + device: { + ua: global.navigator.userAgent, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2 + }, + cur: ['USD'] + }; +} -module.exports = BeachfrontAdapter; +registerBidder(spec); diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md new file mode 100644 index 00000000000..27c28d6c86e --- /dev/null +++ b/modules/beachfrontBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +Module Name: Beachfront Bid Adapter + +Module Type: Bidder Adapter + +Maintainer: johnsalis@beachfront.com + +# Description + +Module that connects to Beachfront's demand sources + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test-video', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [ + { + bidder: 'beachfront', + params: { + bidfloor: 0.01, + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 3c9b6d47e9c..43df639613f 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,131 +1,149 @@ import { expect } from 'chai'; -import BeachfrontAdapter from 'modules/beachfrontBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const ENDPOINT = '//reachms.bfmio.com/bid.json?exchange_id=11bc5dd5-7421-4dd8-c926-40fa653bec76'; - -const REQUEST = { - 'width': 640, - 'height': 480, - 'bidId': '2a1444be20bb2c', - 'bidder': 'beachfront', - 'bidderRequestId': '7101db09af0db2', - 'params': { - 'appId': 'whatever', - 'video': {}, - 'placementCode': 'video', - 'sizes': [ - 640, 480 - ] - }, - 'bids': [ - { - 'bidFloor': 0.01, - 'bidder': 'beachfront', - 'params': { - 'appId': '11bc5dd5-7421-4dd8-c926-40fa653bec76', - 'bidfloor': 0.01, - 'dev': true - }, - 'placementCode': 'video', - 'sizes': [640, 480], - 'bidId': '2a1444be20bb2c', - 'bidderRequestId': '7101db09af0db2', - 'requestId': '979b659e-ecff-46b8-ae03-7251bae4b725' - } - ], - 'requestId': '979b659e-ecff-46b8-ae03-7251bae4b725', -}; -var RESPONSE = { - 'bidPrice': 5.00, - 'url': 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da:0a47f4ce-d91f-48d0-bd1c-64fa2c196f13:2.90&dsp=58bf26882aba5e6ad608beda,0.612&i_type=pre' -}; +import { spec, ENDPOINT } from 'modules/beachfrontBidAdapter'; +import * as utils from 'src/utils'; describe('BeachfrontAdapter', () => { - let adapter; - - beforeEach(() => adapter = new BeachfrontAdapter()); + let bidRequest; + + beforeEach(() => { + bidRequest = { + bidder: 'beachfront', + params: { + bidfloor: 5.00, + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }, + adUnitCode: 'adunit-code', + sizes: [ 640, 480 ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); - describe('request function', () => { - let xhr; - let requests; - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - afterEach(() => xhr.restore()); + it('should return false when the "bidfloor" param is missing', () => { + bidRequest.params = { + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + it('should return false when the "appId" param is missing', () => { + bidRequest.params = { + bidfloor: 5.00 + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('requires parameters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; + it('should return false when no bid params are passed', () => { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(REQUEST); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('POST'); + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); }); }); - describe('response handler', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); + describe('spec.buildRequests', () => { + it('should create a POST request for every bid', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT + bidRequest.params.appId); }); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); + it('should attach the bid request object', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].bidRequest).to.equal(bidRequest); }); - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should attach request data', () => { + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + expect(data.isPrebid).to.equal(true); + expect(data.appId).to.equal(bidRequest.params.appId); + expect(data.domain).to.equal(document.location.hostname); + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.site).to.deep.equal({ page: utils.getTopWindowLocation().host }); + expect(data.device).to.deep.contain({ ua: navigator.userAgent }); + expect(data.cur).to.deep.equal(['USD']); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 5.00); + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + bidRequest.sizes = [[ width, height ]]; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); }); - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'bidPrice': 5.00 - })); + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + bidRequest.sizes = `${width}x${height}`; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: width, h: height }); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('must handle an empty bid size', () => { + bidRequest.sizes = []; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.equal({ w: undefined, h: undefined }); + }); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + describe('spec.interpretResponse', () => { + it('should return no bids if the response is not valid', () => { + const bidResponse = spec.interpretResponse(null, { bidRequest }); + expect(bidResponse.length).to.equal(0); }); - it('handles JSON.parse errors', () => { - server.respondWith(''); + it('should return no bids if the response "url" is missing', () => { + const serverResponse = { + bidPrice: 5.00 + }; + const bidResponse = spec.interpretResponse(serverResponse, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should return no bids if the response "bidPrice" is missing', () => { + const serverResponse = { + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da' + }; + const bidResponse = spec.interpretResponse(serverResponse, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + it('should return a valid bid response', () => { + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + cmpId: '123abc' + }; + const bidResponse = spec.interpretResponse(serverResponse, { bidRequest }); + expect(bidResponse).to.deep.equal({ + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.bidPrice, + creativeId: serverResponse.cmpId, + vastUrl: serverResponse.url, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }); }); }); }); From b721d6fd7d5b8b63d3fdcae69b740f60a2cb3f83 Mon Sep 17 00:00:00 2001 From: Rich Loveland Date: Thu, 19 Oct 2017 17:04:55 -0400 Subject: [PATCH 03/62] Update JSDoc to call the module `pbjs` (#1572) * Update JSDoc to call the module `pbjs` (instead of `$$PREBID_GLOBAL$$``) * Add alias to moar functions, per feedback --- src/prebid.js | 69 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 5e3168a829d..54dc9c55118 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,4 +1,4 @@ -/** @module $$PREBID_GLOBAL$$ */ +/** @module pbjs */ import { getGlobal } from './prebidGlobal'; import { flatten, uniques, isGptPubadsDefined, adUnitsFilter } from './utils'; @@ -117,7 +117,7 @@ function setRenderSize(doc, width, height) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr + * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr * @return {Array} returnObj return bids array */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { @@ -135,6 +135,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { @@ -144,7 +145,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { /** * returns all ad server targeting for all ad units * @return {Object} Map of adUnitCodes and targeting values [] - * @alias module:$$PREBID_GLOBAL$$.getAdserverTargeting + * @alias module:pbjs.getAdserverTargeting */ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { @@ -169,7 +170,7 @@ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { /** * This function returns the bid responses at the given moment. - * @alias module:$$PREBID_GLOBAL$$.getBidResponses + * @alias module:pbjs.getBidResponses * @return {Object} map | object that contains the bidResponses */ @@ -196,7 +197,7 @@ $$PREBID_GLOBAL$$.getBidResponses = function () { /** * Returns bidResponses for the specified adUnitCode * @param {string} adUnitCode adUnitCode - * @alias module:$$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode + * @alias module:pbjs.getBidResponsesForAdUnitCode * @return {Object} bidResponse object */ @@ -210,7 +211,7 @@ $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { /** * Set query string targeting on one or more GPT ad units. * @param {(string|string[])} adUnit a single `adUnit.code` or multiple. - * @alias module:$$PREBID_GLOBAL$$.setTargetingForGPTAsync + * @alias module:pbjs.setTargetingForGPTAsync */ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); @@ -232,6 +233,10 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit) { events.emit(SET_TARGETING); }; +/** + * Set query string targeting on all AST (AppNexus Seller Tag) ad units. Note that this function has to be called after all ad units on page are defined. For working example code, see [Using Prebid.js with AppNexus Publisher Ad Server](http://prebid.org/dev-docs/examples/use-prebid-with-appnexus-ad-server.html). + * @alias module:pbjs.setTargetingForAst + */ $$PREBID_GLOBAL$$.setTargetingForAst = function() { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { @@ -247,7 +252,7 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function() { /** * Returns a bool if all the bids have returned or timed out - * @alias module:$$PREBID_GLOBAL$$.allBidsAvailable + * @alias module:pbjs.allBidsAvailable * @return {bool} all bids available * * @deprecated This function will be removed in Prebid 1.0 @@ -265,7 +270,7 @@ $$PREBID_GLOBAL$$.allBidsAvailable = function () { * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously * @param {HTMLDocument} doc document * @param {string} id bid id to locate the ad - * @alias module:$$PREBID_GLOBAL$$.renderAd + * @alias module:pbjs.renderAd */ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); @@ -321,7 +326,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { /** * Remove adUnit from the $$PREBID_GLOBAL$$ configuration * @param {string} adUnitCode the adUnitCode to remove - * @alias module:$$PREBID_GLOBAL$$.removeAdUnit + * @alias module:pbjs.removeAdUnit */ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); @@ -334,6 +339,9 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { } }; +/** + * @alias module:pbjs.clearAuction + */ $$PREBID_GLOBAL$$.clearAuction = function() { auctionRunning = false; // Only automatically sync if the publisher has not chosen to "enableOverride" @@ -355,6 +363,7 @@ $$PREBID_GLOBAL$$.clearAuction = function() { * @param {number} requestOptions.timeout * @param {Array} requestOptions.adUnits * @param {Array} requestOptions.adUnitCodes + * @alias module:pbjs.requestBids */ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes } = {}) { events.emit('requestBids'); @@ -434,7 +443,7 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a * * Add adunit(s) * @param {Array|Object} adUnitArr Array of adUnits or single adUnit Object. - * @alias module:$$PREBID_GLOBAL$$.addAdUnits + * @alias module:pbjs.addAdUnits */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); @@ -456,6 +465,7 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { * @param {string} event the name of the event * @param {Function} handler a callback to set on event * @param {string} id an identifier in the context of the event + * @alias module:pbjs.onEvent * * This API call allows you to register a callback to handle a Prebid.js event. * An optional `id` parameter provides more finely-grained event callback registration. @@ -486,6 +496,7 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { * @param {string} event the name of the event * @param {Function} handler a callback to remove from the event * @param {string} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) + * @alias module:pbjs.offEvent */ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); @@ -500,7 +511,7 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { * Add a callback event * @param {string} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" * @param {Function} func function to execute. Parameters passed into the function: (bidResObj), [adUnitCode]); - * @alias module:$$PREBID_GLOBAL$$.addCallback + * @alias module:pbjs.addCallback * @returns {string} id for callback * * @deprecated This function will be removed in Prebid 1.0 @@ -523,7 +534,7 @@ $$PREBID_GLOBAL$$.addCallback = function (eventStr, func) { /** * Remove a callback event * //@param {string} cbId id of the callback to remove - * @alias module:$$PREBID_GLOBAL$$.removeCallback + * @alias module:pbjs.removeCallback * @returns {string} id for callback * * @deprecated This function will be removed in Prebid 1.0 @@ -539,6 +550,7 @@ $$PREBID_GLOBAL$$.removeCallback = function (/* cbId */) { * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) * @param {Function} bidderAdaptor [description] * @param {string} bidderCode [description] + * @alias module:pbjs.registerBidAdapter */ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); @@ -552,6 +564,7 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { /** * Wrapper to register analyticsAdapter externally (adaptermanager.registerAnalyticsAdapter()) * @param {Object} options [description] + * @alias module:pbjs.registerAnalyticsAdapter */ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); @@ -562,6 +575,9 @@ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { } }; +/** + * @alias module:pbjs.bidsAvailableForAdapter +*/ $$PREBID_GLOBAL$$.bidsAvailableForAdapter = function (bidderCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.bidsAvailableForAdapter', arguments); @@ -578,6 +594,7 @@ $$PREBID_GLOBAL$$.bidsAvailableForAdapter = function (bidderCode) { /** * Wrapper to bidfactory.createBid() * @param {string} statusCode [description] + * @alias module:pbjs.createBid * @return {Object} bidResponse [description] */ $$PREBID_GLOBAL$$.createBid = function (statusCode) { @@ -589,7 +606,7 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { * Wrapper to bidmanager.addBidResponse * @param {string} adUnitCode [description] * @param {Object} bid [description] - * + * @alias module:pbjs.addBidResponse * @deprecated This function will be removed in Prebid 1.0 * Each bidder will be passed a reference to addBidResponse function in callBids as an argument. * See https://github.com/prebid/Prebid.js/issues/1087 for more details. @@ -604,6 +621,7 @@ $$PREBID_GLOBAL$$.addBidResponse = function (adUnitCode, bid) { * Wrapper to adloader.loadScript * @param {string} tagSrc [description] * @param {Function} callback [description] + * @alias module:pbjs.loadScript */ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.loadScript', arguments); @@ -622,6 +640,7 @@ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { * @param {Object} config * @param {string} config.provider The name of the provider, e.g., `"ga"` for Google Analytics. * @param {Object} config.options The options for this particular analytics adapter. This will likely vary between adapters. + * @alias module:pbjs.enableAnalytics */ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { if (config && !utils.isEmpty(config)) { @@ -632,6 +651,9 @@ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { } }; +/** + * @alias module:pbjs.aliasBidder + */ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { @@ -644,6 +666,7 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { /** * Sets a default price granularity scheme. * @param {string|Object} granularity - the granularity scheme. + * @alias module:pbjs.setPriceGranularity * @deprecated - use pbjs.setConfig({ priceGranularity: }) * "low": $0.50 increments, capped at $5 CPM * "medium": $0.10 increments, capped at $20 CPM (the default) @@ -661,12 +684,16 @@ $$PREBID_GLOBAL$$.setPriceGranularity = function (granularity) { config.setConfig({ priceGranularity: granularity }); }; -/** @deprecated - use pbjs.setConfig({ enableSendAllBids: }) */ +/** + * @alias module:pbjs.enableSendAllBids + * @deprecated - use pbjs.setConfig({ enableSendAllBids: }) +*/ $$PREBID_GLOBAL$$.enableSendAllBids = function () { config.setConfig({ enableSendAllBids: true }); }; /** + * @alias module:pbjs.getAllWinningBids * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse * @property {string} pbAg Auto granularity price bucket; CPM <= 5 ? increment = 0.05 : CPM > 5 && CPM <= 10 ? increment = 0.10 : CPM > 10 && CPM <= 20 ? increment = 0.50 : CPM > 20 ? priceCap = 20.00. Example: `"0.80"`. @@ -713,7 +740,7 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () { * Build master video tag from publishers adserver tag * @param {string} adserverTag default url * @param {Object} options options for video tag - * + * @alias module:pbjs.buildMasterVideoTagFromAdserverTag * @deprecated Include the dfpVideoSupport module in your build, and use the $$PREBID_GLOBAL$$.adservers.dfp.buildVideoAdUrl function instead. * This function will be removed in Prebid 1.0. */ @@ -751,6 +778,7 @@ $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag = function (adserverTag, op * If never called, Prebid will use "random" as the default. * * @param {string} order One of the valid orders, described above. + * @alias module:pbjs.setBidderSequence * @deprecated - use pbjs.setConfig({ bidderSequence: }) */ $$PREBID_GLOBAL$$.setBidderSequence = adaptermanager.setBidderSequence; @@ -759,6 +787,7 @@ $$PREBID_GLOBAL$$.setBidderSequence = adaptermanager.setBidderSequence; * Get array of highest cpm bids for all adUnits, or highest cpm bid * object for the given adUnit * @param {string} adUnitCode - optional ad unit code + * @alias module:pbjs.getHighestCpmBids * @return {Array} array containing highest cpm bid object(s) */ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { @@ -777,6 +806,7 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { * @property {string} [adapter] adapter code to use for S2S * @property {string} [syncEndpoint] endpoint URL for syncing cookies * @property {boolean} [cookieSet] enables cookieSet functionality + * @alias module:pbjs.setS2SConfig */ $$PREBID_GLOBAL$$.setS2SConfig = function(options) { if (!utils.contains(Object.keys(options), 'accountId')) { @@ -805,6 +835,7 @@ $$PREBID_GLOBAL$$.setS2SConfig = function(options) { /** * Get Prebid config options * @param {Object} options + * @alias module:pbjs.getConfig */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; @@ -841,6 +872,7 @@ $$PREBID_GLOBAL$$.getConfig = config.getConfig; * @param {string} options.publisherDomain The publisher's domain where Prebid is running, for cross-domain iFrame communication. Example: `pbjs.setConfig({ publisherDomain: "https://www.theverge.com" })`. * @param {number} options.cookieSyncDelay A delay (in milliseconds) for requesting cookie sync to stay out of the critical path of page load. Example: `pbjs.setConfig({ cookieSyncDelay: 100 })`. * @param {Object} options.s2sConfig The configuration object for [server-to-server header bidding](http://prebid.org/dev-docs/get-started-with-prebid-server.html). Example: + * @alias module:pbjs.setConfig * ``` * pbjs.setConfig({ * s2sConfig: { @@ -873,10 +905,10 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * by prebid once it's done loading. If it runs after prebid loads, then this monkey-patch causes their * function to execute immediately. * - * @memberof $$PREBID_GLOBAL$$ + * @memberof pbjs * @param {function} command A function which takes no arguments. This is guaranteed to run exactly once, and only after * the Prebid script has been fully loaded. - * @alias module:$$PREBID_GLOBAL$$.cmd.push + * @alias module:pbjs.cmd.push */ $$PREBID_GLOBAL$$.cmd.push = function(command) { if (typeof command === 'function') { @@ -905,6 +937,9 @@ function processQueue(queue) { }); } +/** + * @alias module:pbjs.processQueue + */ $$PREBID_GLOBAL$$.processQueue = function() { processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); From 37b218a65bd3cb2903dbd002382a9dc751423622 Mon Sep 17 00:00:00 2001 From: Igor Tchibirev Date: Fri, 20 Oct 2017 09:53:56 -0400 Subject: [PATCH 04/62] realvuBidAdapter (#1571) * init commit realvuAnalyticsAdapter.js * realvuBidAdapter.js initial commit * realvuBidAdapter fixed for latest version * realvuBidAdapter unit_id:bid.code * tabs and spaces * test implemented * postpone analytics * try-catch for top * appnexusBidAdapter inline * var replaced with const in require statements * unwanted code removed, test fixed * lint and test-coverage fix * lint fix --- modules/realvuBidAdapter.js | 239 +++++++++++++++++++++ test/spec/modules/realvuBidAdapter_spec.js | 61 ++++++ yarn.lock | 4 + 3 files changed, 304 insertions(+) create mode 100644 modules/realvuBidAdapter.js create mode 100644 test/spec/modules/realvuBidAdapter_spec.js diff --git a/modules/realvuBidAdapter.js b/modules/realvuBidAdapter.js new file mode 100644 index 00000000000..b3e0e4f043f --- /dev/null +++ b/modules/realvuBidAdapter.js @@ -0,0 +1,239 @@ +import { getBidRequest } from 'src/utils'; +import adaptermanager from 'src/adaptermanager'; + +const CONSTANTS = require('src/constants'); +const utils = require('src/utils.js'); +const adloader = require('src/adloader.js'); +const bidmanager = require('src/bidmanager.js'); +const bidfactory = require('src/bidfactory.js'); +const Adapter = require('src/adapter.js').default; + +var RealVuAdapter = function RealVuAdapter() { + var baseAdapter = new Adapter('realvu'); + baseAdapter.callBids = function (params) { + var pbids = params.bids; + var boost_back = function() { + var top1 = window; + realvu_frm = 0; + try { + var wnd = window; + while ((top1 != top) && (typeof (wnd.document) != 'undefined')) { + top1 = wnd; + wnd = wnd.parent; + } + } catch (e) { }; + for (var i = 0; i < pbids.length; i++) { + var bid_rq = pbids[i]; + var sizes = utils.parseSizesInput(bid_rq.sizes); + top1.realvu_boost.addUnitById({ + partner_id: bid_rq.params.partnerId, + unit_id: bid_rq.placementCode, + callback: baseAdapter.boostCall, + pbjs_bid: bid_rq, + size: sizes[0], + mode: 'kvp' + }); + } + }; + adloader.loadScript('//ac.realvu.net/realvu_boost.js', boost_back, 1); + }; + + baseAdapter.boostCall = function(rez) { + var bid_request = rez.pin.pbjs_bid; + var callbackId = bid_request.bidId; + if (rez.realvu === 'yes') { + var adap = new RvAppNexusAdapter(); + adloader.loadScript(adap.buildJPTCall(bid_request, callbackId)); + } else { // not in view - respond with no bid. + var adResponse = bidfactory.createBid(2); + adResponse.bidderCode = 'realvu'; + bidmanager.addBidResponse(bid_request.placementCode, adResponse); + } + }; + + // +copy/pasted appnexusBidAdapter, "handleAnCB" replaced with "handleRvAnCB" + var RvAppNexusAdapter = function RvAppNexusAdapter() { + var usersync = false; + + this.buildJPTCall = function (bid, callbackId) { + // determine tag params + var placementId = utils.getBidIdParameter('placementId', bid.params); + + // memberId will be deprecated, use member instead + var memberId = utils.getBidIdParameter('memberId', bid.params); + var member = utils.getBidIdParameter('member', bid.params); + var inventoryCode = utils.getBidIdParameter('invCode', bid.params); + var query = utils.getBidIdParameter('query', bid.params); + var referrer = utils.getBidIdParameter('referrer', bid.params); + var altReferrer = utils.getBidIdParameter('alt_referrer', bid.params); + var jptCall = '//ib.adnxs.com/jpt?'; + + jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleRvAnCB'); + jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); + jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); + jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + if (member) { + jptCall = utils.tryAppendQueryString(jptCall, 'member', member); + } else if (memberId) { + jptCall = utils.tryAppendQueryString(jptCall, 'member', memberId); + utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + jptCall = utils.tryAppendQueryString(jptCall, 'traffic_source_code', (utils.getBidIdParameter('trafficSourceCode', bid.params))); + + // sizes takes a bit more logic + var sizeQueryString = ''; + var parsedSizes = utils.parseSizesInput(bid.sizes); + + // combine string into proper querystring for impbus + var parsedSizesLength = parsedSizes.length; + if (parsedSizesLength > 0) { + // first value should be "size" + sizeQueryString = 'size=' + parsedSizes[0]; + if (parsedSizesLength > 1) { + // any subsequent values should be "promo_sizes" + sizeQueryString += '&promo_sizes='; + for (var j = 1; j < parsedSizesLength; j++) { + sizeQueryString += parsedSizes[j] += ','; + } + + // remove trailing comma + if (sizeQueryString && sizeQueryString.charAt(sizeQueryString.length - 1) === ',') { + sizeQueryString = sizeQueryString.slice(0, sizeQueryString.length - 1); + } + } + } + + if (sizeQueryString) { + jptCall += sizeQueryString + '&'; + } + + // this will be deprecated soon + var targetingParams = utils.parseQueryStringParameters(query); + + if (targetingParams) { + // don't append a & here, we have already done it in parseQueryStringParameters + jptCall += targetingParams; + } + + // append custom attributes: + var paramsCopy = Object.assign({}, bid.params); + + // delete attributes already used + delete paramsCopy.placementId; + delete paramsCopy.memberId; + delete paramsCopy.invCode; + delete paramsCopy.query; + delete paramsCopy.referrer; + delete paramsCopy.alt_referrer; + delete paramsCopy.member; + + // get the reminder + var queryParams = utils.parseQueryStringParameters(paramsCopy); + + // append + if (queryParams) { + jptCall += queryParams; + } + + // append referrer + if (referrer === '') { + referrer = utils.getTopWindowUrl(); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'referrer', referrer); + jptCall = utils.tryAppendQueryString(jptCall, 'alt_referrer', altReferrer); + + // remove the trailing "&" + if (jptCall.lastIndexOf('&') === jptCall.length - 1) { + jptCall = jptCall.substring(0, jptCall.length - 1); + } + + // @if NODE_ENV='debug' + utils.logMessage('jpt request built: ' + jptCall); + // @endif + + // append a timer here to track latency + bid.startTime = new Date().getTime(); + + return jptCall; + } + + // expose the callback to the global object: + $$PREBID_GLOBAL$$.handleRvAnCB = function (jptResponseObj) { + var bidCode; + + if (jptResponseObj && jptResponseObj.callback_uid) { + var responseCPM; + var id = jptResponseObj.callback_uid; + var placementCode = ''; + var bidObj = getBidRequest(id); + if (bidObj) { + bidCode = bidObj.bidder; + + placementCode = bidObj.placementCode; + + // set the status + bidObj.status = CONSTANTS.STATUS.GOOD; + } + + // @if NODE_ENV='debug' + utils.logMessage('JSONP callback function called for ad ID: ' + id); + + // @endif + var bid = []; + if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { + responseCPM = parseInt(jptResponseObj.result.cpm, 10); + + // CPM response from /jpt is dollar/cent multiplied by 10000 + // in order to avoid using floats + // switch CPM to "dollar/cent" + responseCPM = responseCPM / 10000; + + // store bid response + // bid status is good (indicating 1) + var adId = jptResponseObj.result.creative_id; + bid = bidfactory.createBid(1, bidObj); + bid.creative_id = adId; + bid.bidderCode = bidCode; + bid.cpm = responseCPM; + bid.adUrl = jptResponseObj.result.ad; + bid.width = jptResponseObj.result.width; + bid.height = jptResponseObj.result.height; + bid.dealId = jptResponseObj.result.deal_id; + + bidmanager.addBidResponse(placementCode, bid); + } else { + // no bid + bid = bidfactory.createBid(2, bidObj); + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + + if (!usersync) { + var iframe = utils.createInvisibleIframe(); + iframe.src = '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html'; + try { + document.body.appendChild(iframe); + } catch (error) { + utils.logError(error); + } + usersync = true; + } + } else { + utils.logMessage('No prebid response for placement %%PLACEMENT%%'); + } + }; + }; + // -copy/pasted appnexusBidAdapter + return Object.assign(this, { + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + boostCall: baseAdapter.boostCall + }); +}; + +adaptermanager.registerBidAdapter(new RealVuAdapter(), 'realvu'); + +module.exports = RealVuAdapter; diff --git a/test/spec/modules/realvuBidAdapter_spec.js b/test/spec/modules/realvuBidAdapter_spec.js new file mode 100644 index 00000000000..36517fa723e --- /dev/null +++ b/test/spec/modules/realvuBidAdapter_spec.js @@ -0,0 +1,61 @@ +import {expect} from 'chai'; +import RealVuAdapter from '../../../modules/realvuBidAdapter'; +import bidmanager from '../../../src/bidmanager'; +import adloader from '../../../src/adloader'; + +describe('RealVu Adapter Test', () => { + let adapter; + + const REQUEST = { + bidderCode: 'realvu', + requestId: '0d67ddab-1502-4897-a7bf-e8078e983405', + bidderRequestId: '1b5e314fe79b1d', + bids: [ + { + bidId: '2d86a04312d95d', + bidder: 'realvu', + bidderRequestId: '1b5e314fe79b1d', + // mediaType:undefined, + params: { + partnerId: '1Y', + placementId: '9339508', + }, + placementCode: 'ad_container_1', + // renderer:undefined, + sizes: [[300, 250]], + transactionId: '0d67ddab-1502-4897-a7bf-e8078e983405' + } + ], + start: 1504628062271 + }; + + var bidResponseStub; + var adloaderStub; + + beforeEach(function() { + bidResponseStub = sinon.stub(bidmanager, 'addBidResponse'); + adloaderStub = sinon.stub(adloader, 'loadScript'); + }); + + afterEach(function() { + adloaderStub.restore(); + bidResponseStub.restore(); + }); + + adapter = new RealVuAdapter(); + + it('load boost', () => { + adapter.callBids(REQUEST); + expect(adloaderStub.getCall(0).args[0]).to.contain('realvu_boost.js'); + }); + + it('callBid "yes"', () => { + adapter.boostCall({realvu: 'yes', pin: {pbjs_bid: REQUEST.bids[0]}}); + expect(adloaderStub.getCall(0).args[0]).to.contain('id=9339508'); + }); + + it('callBid "no"', () => { + adapter.boostCall({realvu: 'no', pin: {pbjs_bid: REQUEST.bids[0]}}); + expect(bidResponseStub.getCall(0).args[1].getStatusCode()).to.equal(2); + }); +}); diff --git a/yarn.lock b/yarn.lock index f6efe31c6a5..30e6b93b544 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4249,6 +4249,10 @@ ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +ignore-loader@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463" + ignore@^3.3.3: version "3.3.5" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" From a7a73fd8228c84c2f377c7ccdbccbbbeba66b67f Mon Sep 17 00:00:00 2001 From: Pieter de Zwart Date: Fri, 20 Oct 2017 14:41:45 -0700 Subject: [PATCH 05/62] Updating license (#1717) --- LICENSE | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index c8daf0f8942..f5028c63317 100644 --- a/LICENSE +++ b/LICENSE @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2013 APPNEXUS INC + Copyright 2017 PREBID.ORG, INC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,4 +188,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. From be21952ee1ffa8f183f718f63d612b6cfdb84908 Mon Sep 17 00:00:00 2001 From: Sebastian Widelak Date: Fri, 20 Oct 2017 23:42:49 +0200 Subject: [PATCH 06/62] Justpremium Adapter bugfix (#1716) * Justpremium adapter and unit tests. * Fix test suit. * Performance improvements. * Changes requested in pull request review. * Register justpremium adapter in adaptermanager * pass through bid from request * fix linting errors * Load polyfills for older browsers * Load polyfills if older browser * Remove package-lock.json --- modules/justpremiumBidAdapter.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index b608461946c..d1ca62cd8e4 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -49,6 +49,12 @@ const JustpremiumAdapter = function JustpremiumAdapter() { return null; } + function isOldBrowser() { + const isPromisse = typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1; + const isWeakMap = typeof WeakMap !== 'undefined' && WeakMap.toString().indexOf('[native code]') !== -1; + return (!Array.prototype.find || !Array.prototype.sort || !Array.prototype.map || !Array.prototype.filter || !Array.prototype.keys || !isPromisse || !isWeakMap); + } + function setupVar() { d = top.document; jPAM = top.jPAM = top.jPAM || window.jPAM || {}; @@ -58,7 +64,7 @@ const JustpremiumAdapter = function JustpremiumAdapter() { server: null }; const libVer = readCookie('jpxhbjs') || null; - toLoad = dConfig.toLoad || [d.location.protocol + '//cdn-cf.justpremium.com/js/' + (libVer ? libVer + '/' : '') + 'jpx.js']; + toLoad = dConfig.toLoad || [d.location.protocol + '//cdn-cf.justpremium.com/js/' + (libVer ? libVer + '/' : '') + (isOldBrowser() ? 'jpxp.js' : 'jpx.js')]; server = dConfig.server || d.location.protocol + '//pre.ads.justpremium.com/v/1.4'; } @@ -114,7 +120,6 @@ const JustpremiumAdapter = function JustpremiumAdapter() { return rec.length ? rec.pop() : false; } } - return false; } From a45387dc9d6bdcfbf1937ab8f42c0871c7c3c284 Mon Sep 17 00:00:00 2001 From: FeatureForward Date: Mon, 23 Oct 2017 19:03:42 +0300 Subject: [PATCH 07/62] Allow more than one placement from one page (#1692) --- modules/featureforwardBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/featureforwardBidAdapter.js b/modules/featureforwardBidAdapter.js index 34d7dddba49..53a3a7c5a08 100644 --- a/modules/featureforwardBidAdapter.js +++ b/modules/featureforwardBidAdapter.js @@ -14,8 +14,8 @@ function FeatureForwardAdapter() { }; function _callBids(bidderRequest) { - var i = 0; bidderRequest.bids.forEach(bidRequest => { + var i = 0; try { while (bidRequest.sizes[i] !== undefined) { var params = Object.assign({}, environment(), bidRequest.params, {'size': bidRequest.sizes[i]}); From 505f7f3ecdc79a5c8fcfbf8fbd123f3848bebd94 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Mon, 23 Oct 2017 11:03:18 -0600 Subject: [PATCH 08/62] fix log message not displaying when referencing missing bidder (#1737) * fix log message not displaying when referencing missing bidder * add test for missing bidder log message * move test to adaptermanager_spec --- src/adaptermanager.js | 14 ++++++------- test/spec/unit/core/adapterManager_spec.js | 24 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 2204e997084..37ab5dffafb 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -197,20 +197,18 @@ exports.callBids = ({adUnits, cbTimeout}) => { $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); _bidderRequests.push(bidderRequest); } + } else { + utils.logError(`Adapter trying to be called which does not exist: ${bidderCode} adaptermanager.callBids`); } }); _bidderRequests.forEach(bidRequest => { bidRequest.start = new Date().getTime(); const adapter = _bidderRegistry[bidRequest.bidderCode]; - if (adapter) { - if (bidRequest.bids && bidRequest.bids.length !== 0) { - utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - adapter.callBids(bidRequest); - } - } else { - utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`); + if (bidRequest.bids && bidRequest.bids.length !== 0) { + utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + adapter.callBids(bidRequest); } }) }; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 6da22ed8984..8476d53af7b 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -34,6 +34,30 @@ var appnexusAdapterMock = { }; describe('adapterManager tests', () => { + describe('callBids', () => { + beforeEach(() => { + sinon.stub(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should log an error if a bidder is used that does not exist', () => { + const adUnits = [{ + code: 'adUnit-code', + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + {bidder: 'fakeBidder', params: {placementId: 'id'}} + ] + }]; + + AdapterManager.callBids({adUnits}); + + sinon.assert.called(utils.logError); + }); + }); + describe('S2S tests', () => { beforeEach(() => { AdapterManager.setS2SConfig(CONFIG); From f8bf19790a243d0d5be340d856da559c35b25d07 Mon Sep 17 00:00:00 2001 From: Nick Narbone Date: Mon, 23 Oct 2017 13:07:51 -0400 Subject: [PATCH 09/62] Fix window.top.host cross origin issue when in nested iframes. (#1730) --- modules/sonobiBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 81745427742..689de8635c9 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -12,7 +12,7 @@ var SonobiAdapter = function SonobiAdapter() { var trinity = 'https://apex.go.sonobi.com/trinity.js?key_maker='; var adSlots = request.bids || []; var bidderRequestId = request.bidderRequestId; - var ref = (window.frameElement) ? '&ref=' + encodeURI(top.location.host || document.referrer) : ''; + var ref = '&ref=' + encodeURI(utils.getTopWindowLocation().host); adloader.loadScript(trinity + JSON.stringify(_keymaker(adSlots)) + '&cv=' + _operator(bidderRequestId) + ref); } From 409fbc5be453a15ad0104db7a755ab9595214206 Mon Sep 17 00:00:00 2001 From: varashellov Date: Mon, 23 Oct 2017 12:32:37 -0700 Subject: [PATCH 10/62] Platform.io Bidder Adapter update. Prebid v1.0. (#1705) * Add PlatformioBidAdapter * Update platformioBidAdapter.js * Add files via upload * Update hello_world.html * Update platformioBidAdapter.js * Update platformioBidAdapter_spec.js * Update hello_world.html * Update platformioBidAdapter_spec.js * Update platformioBidAdapter.js * Update hello_world.html * Add files via upload * Update platformioBidAdapter ## Type of change - [x] Other ## Description of change 1. RequestURL changes 2. Add placementCode to request params * Update platformioBidAdapter * Update platformioBidAdapter ## Type of change - [x] Other ## Description of change 1. RequestURL changes 2. Add placementCode to request params * Add files via upload * Add files via upload * Add files via upload * Update platformioBidAdapter.js Endpoint URL change * Update platformioBidAdapter_spec.js Endpoint URL change * Update platformioBidAdapter_spec.js * Update platformioBidAdapter_spec.js * Update platformioBidAdapter.js * Update platformioBidAdapter.js * Update platformioBidAdapter_spec.js * Add files via upload * Add files via upload * Add files via upload --- modules/platformioBidAdapter.js | 188 ++++++++----- modules/platformioBidAdapter.md | 27 ++ .../spec/modules/platformioBidAdapter_spec.js | 250 +++++++----------- 3 files changed, 246 insertions(+), 219 deletions(-) create mode 100644 modules/platformioBidAdapter.md diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js index 87ebb2b61c4..c33551ed396 100644 --- a/modules/platformioBidAdapter.js +++ b/modules/platformioBidAdapter.js @@ -1,63 +1,125 @@ -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var utils = require('src/utils.js'); -var CONSTANTS = require('src/constants.json'); -var adaptermanager = require('src/adaptermanager'); - -var PlatformIOAdapter = function PlatformIOAdapter() { - function _callBids(params) { - var bidURL; - var bids = params.bids || []; - var requestURL = window.location.protocol + '//js.adx1.com/pb_ortb.js?cb=' + new Date().getTime() + '&ver=1&'; - - for (var i = 0; i < bids.length; i++) { - var requestParams = {}; - var bid = bids[i]; - - requestParams.pub_id = bid.params.pubId; - requestParams.site_id = bid.params.siteId; - requestParams.placement_id = bid.placementCode; - - var parseSized = utils.parseSizesInput(bid.sizes); - var arrSize = parseSized[0].split('x'); - - requestParams.width = arrSize[0]; - requestParams.height = arrSize[1]; - requestParams.callback = '$$PREBID_GLOBAL$$._doPlatformIOCallback'; - requestParams.callback_uid = bid.bidId; - bidURL = requestURL + utils.parseQueryStringParameters(requestParams); - - utils.logMessage('PlatformIO.prebid, Bid ID: ' + bid.bidId + ', Pub ID: ' + bid.params.pubId); - adloader.loadScript(bidURL); - } - } - - $$PREBID_GLOBAL$$._doPlatformIOCallback = function (response) { - var bidObject; - var bidRequest; - var callbackID; - callbackID = response.callback_uid; - bidRequest = utils.getBidRequest(callbackID); - if (response.cpm > 0) { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidRequest); - bidObject.bidderCode = 'platformio'; - bidObject.cpm = response.cpm; - bidObject.ad = response.tag; - bidObject.width = response.width; - bidObject.height = response.height; - } else { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequest); - bidObject.bidderCode = 'platformio'; - utils.logMessage('No Bid response from Platformio request: ' + callbackID); - } - bidmanager.addBidResponse(bidRequest.placementCode, bidObject); - }; - - return { - callBids: _callBids - }; -}; -adaptermanager.registerBidAdapter(new PlatformIOAdapter(), 'platformio'); - -module.exports = PlatformIOAdapter; + +import {logError, getTopWindowLocation} from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +export const spec = { + + code: 'platformio', + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.pubId && bid.params.siteId) + ), + buildRequests: bidRequests => { + const request = { + id: bidRequests[0].bidderRequestId, + at: 2, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests), + device: device(), + }; + return { + method: 'POST', + url: '//piohbdisp.hb.adx1.com/', + data: JSON.stringify(request), + }; + }, + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response) + ), +}; +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + const ortbRequest = parse(bidRequest.data); + ortbRequest.imp.forEach(imp => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach(id => { + if (idToBidMap[id]) { + const bid = { + requestId: id, + cpm: idToBidMap[id].price, + creative_id: id, + creativeId: id, + adId: id, + }; + bid.ad = idToBidMap[id].adm; + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + bids.push(bid); + } + }); + return bids; +} +function impression(slot) { + return { + id: slot.bidId, + banner: banner(slot), + bidfloor: '0.000001', + tagid: slot.params.placementId.toString(), + }; +} +function banner(slot) { + const size = slot.params.size.toUpperCase().split('X'); + const width = parseInt(size[0]); + const height = parseInt(size[1]); + return { + w: width, + h: height, + }; +} +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + const appParams = bidderRequest[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + domain: getTopWindowLocation().hostname, + }, + id: siteId.toString(), + ref: referrer(), + page: getTopWindowLocation().href, + } + } + return null; +} +function referrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} +function device() { + return { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + }; +} +function parse(rawResponse) { + try { + if (rawResponse) { + return JSON.parse(rawResponse); + } + } catch (ex) { + logError('platformio.parse', 'ERROR', ex); + } + return null; +} + +registerBidder(spec); diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md new file mode 100644 index 00000000000..74f6adbf256 --- /dev/null +++ b/modules/platformioBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +**Module Name**: Platform.io Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: sk@ultralab.by + +# Description + +Connects to Platform.io demand source to fetch bids. +Please use ```platformio``` as the bidder code. + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'platformio', + params: { + pubId: '28082', + siteId: '26047', + placementId: '123', + size: '250X250' + } + }] + }]; +``` diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js index 01a6176c58d..1b07b4049d4 100644 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -1,156 +1,94 @@ -describe('platformio adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/platformioBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - - var stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - - it('bid request for single placement', function () { - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'platformio', - params: { pubId: '37054', siteId: '123' } - }] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - expect(parsedBidUrlQueryString).to.have.property('pub_id').and.to.equal('37054'); - expect(parsedBidUrlQueryString).to.have.property('site_id').and.to.equal('123'); - expect(parsedBidUrlQueryString).to.have.property('width').and.to.equal('300'); - expect(parsedBidUrlQueryString).to.have.property('height').and.to.equal('250'); - }); - }); - - describe('handling bid response', function () { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'platformio', - params: { pubId: '37054', siteId: '123' } - }] - }; - - var response = { - cpm: 1, - width: 300, - height: 250, - callback_uid: 'bid1111', - tag: ' '; + const CPM = 1; + + function getBid(isValid) { + return { + bidder: BIDDER_CODE, + params: (function () { + return { + [SECURITY]: isValid === true ? 'sec1' : null, + [DATA_PARTNER_ID]: 'dpid1', + [DATA_PARTNER_PIXEL_ID]: 'pid1', + [ALG]: 'ihr', + [NQ]: SEARCH_QUERY, + [NQ_NAME]: null, + [CATEGORY]: null, + } + })(), + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'ee335735-ddd3-41f2-b6c6-e8aa99f81c0f', + sizes: SIZES, + bidId: '24a1c9ec270973', + bidderRequestId: '189135372acd55', + requestId: 'ac15bb68-4ef0-477f-93f4-de91c47f00a9' + } + } + + const SINGlE_BID_REQUEST = { + [SECURITY]: 'sec1', + [DATA_PARTNER_ID]: 'dpid1', + [DATA_PARTNER_PIXEL_ID]: 'pid1', + [ALG]: 'ihr', + [NQ]: [SEARCH_QUERY, null], + sizes: [WIDTH + 'x' + HEIGHT], + bidId: '24a1c9ec270973', + cors: 'http://localhost:9876' + }; + + function getSingleBidResponse(isValid) { + return { + id: '24a1c9ec270973', + cpm: isValid === true ? CPM : null, + width: WIDTH, + height: HEIGHT, + ad: AD, + ttl: 360, + creativeId: 'TEST_ID', + netRevenue: false, + currency: 'EUR', + } + } + + const VALID_BID = { + requestId: '24a1c9ec270973', + cpm: CPM, + width: WIDTH, + height: HEIGHT, + ad: AD, + ttl: 360, + creativeId: 'TEST_ID', + netRevenue: false, + currency: 'EUR', + }; + + describe('NanoAdapter', () => { + let nanoBidAdapter = spec; + + describe('Methods', () => { + it('Test isBidRequestValid() with valid param', function () { + expect(nanoBidAdapter.isBidRequestValid(getBid(true))).to.equal(true); + }); + it('Test isBidRequestValid() with invalid param', function () { + expect(nanoBidAdapter.isBidRequestValid(getBid(false))).to.equal(false); + }); + it('Test buildRequests()', function () { + let request = nanoBidAdapter.buildRequests([getBid(true)]); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENGINE_BASE_URL); + expect(request.data).to.equal(JSON.stringify([SINGlE_BID_REQUEST])); + }); + it('Test interpretResponse() length', function () { + let bids = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)]); + expect(bids.length).to.equal(1); + }); + it('Test interpretResponse() bids', function () { + let bid = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)])[0]; + expect(bid.requestId).to.equal(VALID_BID.requestId); + expect(bid.cpm).to.equal(VALID_BID.cpm); + expect(bid.width).to.equal(VALID_BID.width); + expect(bid.height).to.equal(VALID_BID.height); + expect(bid.ad).to.equal(VALID_BID.ad); + expect(bid.ttl).to.equal(VALID_BID.ttl); + expect(bid.creativeId).to.equal(VALID_BID.creativeId); + expect(bid.currency).to.equal(VALID_BID.currency); + }); + }); + }); +}); From 15b2798924700abbb6b81c71d626ba8db5be96a6 Mon Sep 17 00:00:00 2001 From: dbemiller Date: Thu, 26 Oct 2017 11:52:22 -0400 Subject: [PATCH 19/62] Fixed the argument type on getUserSyncs. (#1767) --- src/adapters/bidderFactory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 87d92cbfb92..241891640c5 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -47,7 +47,7 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {function(ServerResponse, BidRequest): Bid[]} interpretResponse Given a successful response from the Server, * interpret it and return the Bid objects. This function will be run inside a try/catch. * If it throws any errors, your bids will be discarded. - * @property {function(SyncOptions, Array): UserSync[]} [getUserSyncs] Given an array of all the responses + * @property {function(SyncOptions, ServerResponse[]): UserSync[]} [getUserSyncs] Given an array of all the responses * from the server, determine which user syncs should occur. The argument array will contain every element * which has been sent through to interpretResponse. The order of syncs in this array matters. The most * important ones should come first, since publishers may limit how many are dropped on their page. From c730f95444aa344a68dfdf2508c75ef83dcb6ca4 Mon Sep 17 00:00:00 2001 From: jbartek-improve <31618107+jbartek-improve@users.noreply.github.com> Date: Thu, 26 Oct 2017 17:53:47 +0200 Subject: [PATCH 20/62] Update Improve Digital adapter for Prebid 1.0 (#1728) * Update Improve Digital adapter for Prebid 1.0 * Removed bidderCode from bids * Added creativeId to bid response; updated format of the first argument of interpretResponse --- modules/improvedigitalBidAdapter.js | 379 ++++----- modules/improvedigitalBidAdapter.md | 47 + .../modules/improvedigitalBidAdapter_spec.js | 804 ++++++------------ 3 files changed, 493 insertions(+), 737 deletions(-) create mode 100644 modules/improvedigitalBidAdapter.md diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 51fb61b03e2..bc00127b269 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,176 +1,150 @@ -const LIB_VERSION_GLOBAL = '3.0.5'; - -const CONSTANTS = require('src/constants'); -const utils = require('src/utils'); -const bidfactory = require('src/bidfactory'); -const bidmanager = require('src/bidmanager'); -const adloader = require('src/adloader'); -const Adapter = require('src/adapter').default; -const adaptermanager = require('src/adaptermanager'); - -const IMPROVE_DIGITAL_BIDDER_CODE = 'improvedigital'; - -const ImproveDigitalAdapter = function () { - let baseAdapter = new Adapter(IMPROVE_DIGITAL_BIDDER_CODE); - baseAdapter.idClient = new ImproveDigitalAdServerJSClient('hb'); - - const LIB_VERSION = LIB_VERSION_GLOBAL; +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { userSync } from 'src/userSync'; + +const BIDDER_CODE = 'improvedigital'; + +export const spec = { + version: '4.0.0', + code: BIDDER_CODE, + aliases: ['id'], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid && bid.params && (bid.params.placementId || (bid.params.placementKey && bid.params.publisherId))); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests) { + let normalizedBids = bidRequests.map((bidRequest) => { + return getNormalizedBidRequest(bidRequest); + }); - // Ad server needs to implement JSONP using this function as the callback - const CALLBACK_FUNCTION = '$$PREBID_GLOBAL$$' + '.improveDigitalResponse'; + let idClient = new ImproveDigitalAdServerJSClient('hb'); + let requestParameters = { + singleRequestMode: false, + httpRequestType: idClient.CONSTANTS.HTTP_REQUEST_TYPE.GET, + returnObjType: idClient.CONSTANTS.RETURN_OBJ_TYPE.PREBID, + libVersion: this.version + }; - baseAdapter.getNormalizedBidRequest = function(bid) { - let adUnitId = utils.getBidIdParameter('placementCode', bid) || null; - let placementId = utils.getBidIdParameter('placementId', bid.params) || null; - let publisherId = null; - let placementKey = null; + let requestObj = idClient.createRequest( + normalizedBids, // requestObject + requestParameters + ); - if (placementId === null) { - publisherId = utils.getBidIdParameter('publisherId', bid.params) || null; - placementKey = utils.getBidIdParameter('placementKey', bid.params) || null; + if (requestObj.errors && requestObj.errors.length > 0) { + utils.logError('ID WARNING 0x01'); } - let keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; - let localSize = utils.getBidIdParameter('size', bid.params) || null; - let bidId = utils.getBidIdParameter('bidId', bid); - let normalizedBidRequest = {}; - if (placementId) { - normalizedBidRequest.placementId = placementId; - } else { - if (publisherId) { - normalizedBidRequest.publisherId = publisherId; - } - if (placementKey) { - normalizedBidRequest.placementKey = placementKey; + return requestObj.requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + const bids = []; + utils._each(serverResponse.body.bid, function (bidObject) { + if (!bidObject.price || bidObject.price === null || + bidObject.hasOwnProperty('errorCode') || + typeof bidObject.adm !== 'string') { + return; } - } - if (keyValues) { - normalizedBidRequest.keyValues = keyValues; - } - if (localSize && localSize.w && localSize.h) { - normalizedBidRequest.size = {}; - normalizedBidRequest.size.h = localSize.h; - normalizedBidRequest.size.w = localSize.w; - } - if (bidId) { - normalizedBidRequest.id = bidId; - } - if (adUnitId) { - normalizedBidRequest.adUnitId = adUnitId; - } - return normalizedBidRequest; - } - - let submitNoBidResponse = function(bidRequest) { - let bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequest); - bid.bidderCode = IMPROVE_DIGITAL_BIDDER_CODE; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - }; - - $$PREBID_GLOBAL$$.improveDigitalResponse = function(response) { - let bidRequests = utils.getBidderRequestAllAdUnits(IMPROVE_DIGITAL_BIDDER_CODE); - if (bidRequests && bidRequests.bids && bidRequests.bids.length > 0) { - utils._each(bidRequests.bids, function (bidRequest) { - let bidObjects = response.bid || []; - utils._each(bidObjects, function (bidObject) { - if (bidObject.id === bidRequest.bidId) { - if (!bidObject.price || bidObject.price === null) { - submitNoBidResponse(bidRequest); - return; - } - if (bidObject.errorCode && bidObject.errorCode !== 0) { - submitNoBidResponse(bidRequest); - return; - } - if (!bidObject.adm || bidObject.adm === null || typeof bidObject.adm !== 'string') { - submitNoBidResponse(bidRequest); - return; - } - - let bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidRequest); - - let syncString = ''; - let syncArray = (bidObject.sync && bidObject.sync.length > 0) ? bidObject.sync : []; + let bid = {}; + let nurl = ''; + if (bidObject.nurl && bidObject.nurl.length > 0) { + nurl = ``; + } + bid.ad = `${nurl}`; + bid.adId = bidObject.id; + bid.cpm = parseFloat(bidObject.price); + bid.creativeId = bidObject.crid; + bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; + if (utils.isNumber(bidObject.lid)) { + bid.dealId = bidObject.lid; + } else if (typeof bidObject.lid === 'object' && bidObject.lid['1']) { + bid.dealId = bidObject.lid['1']; + } + bid.height = bidObject.h; + bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; + bid.requestId = bidObject.id; + bid.width = bidObject.w; - utils._each(syncArray, function (syncElement) { - let syncInd = syncElement.replace(/\//g, '\\\/'); - syncString = `${syncString}${(syncString === '') ? 'document.writeln(\"' : ''}`; - }); - syncString = `${syncString}${(syncString === '') ? '' : '\")'}`; + bids.push(bid); - let nurl = ''; - if (bidObject.nurl && bidObject.nurl.length > 0) { - nurl = ``; - } - bid.ad = `${nurl}`; - bid.bidderCode = IMPROVE_DIGITAL_BIDDER_CODE; - bid.cpm = parseFloat(bidObject.price); - bid.width = bidObject.w; - bid.height = bidObject.h; - - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } + // Register user sync URLs + if (utils.isArray(bidObject.sync)) { + utils._each(bidObject.sync, function (syncElement) { + userSync.registerSync('image', spec.code, syncElement); }); - }); - } - }; - - baseAdapter.callBids = function (params) { - // params will contain an array - let bidRequests = params.bids || []; - let loc = utils.getTopWindowLocation(); - let requestParameters = { - singleRequestMode: false, - httpRequestType: this.idClient.CONSTANTS.HTTP_REQUEST_TYPE.GET, - callback: CALLBACK_FUNCTION, - secure: (loc.protocol === 'https:') ? 1 : 0, - libVersion: this.LIB_VERSION - }; - - let normalizedBids = bidRequests.map((bidRequest) => { - let normalizedBidRequest = this.getNormalizedBidRequest(bidRequest); - if (bidRequest.params && bidRequest.params.singleRequest) { - requestParameters.singleRequestMode = true; } - return normalizedBidRequest; }); + return bids; + } +}; - let request = this.idClient.createRequest( - normalizedBids, // requestObject - requestParameters - ); +function getNormalizedBidRequest(bid) { + let adUnitId = utils.getBidIdParameter('adUnitCode', bid) || null; + let placementId = utils.getBidIdParameter('placementId', bid.params) || null; + let publisherId = null; + let placementKey = null; - if (request.errors && request.errors.length > 0) { - utils.logError('ID WARNING 0x01'); - } + if (placementId === null) { + publisherId = utils.getBidIdParameter('publisherId', bid.params) || null; + placementKey = utils.getBidIdParameter('placementKey', bid.params) || null; + } + let keyValues = utils.getBidIdParameter('keyValues', bid.params) || null; + let localSize = utils.getBidIdParameter('size', bid.params) || null; + let bidId = utils.getBidIdParameter('bidId', bid); + let transactionId = utils.getBidIdParameter('transactionId', bid); - if (request && request.requests && request.requests[0]) { - utils._each(request.requests, function (requestElement) { - if (requestElement.url) { - adloader.loadScript(requestElement.url, null); - } - }); + let normalizedBidRequest = {}; + if (placementId) { + normalizedBidRequest.placementId = placementId; + } else { + if (publisherId) { + normalizedBidRequest.publisherId = publisherId; + } + if (placementKey) { + normalizedBidRequest.placementKey = placementKey; } } - // Export the callBids function, so that prebid.js can execute this function - // when the page asks to send out bid requests. - return Object.assign(this, { - LIB_VERSION: LIB_VERSION, - idClient: baseAdapter.idClient, - getNormalizedBidRequest: baseAdapter.getNormalizedBidRequest, - callBids: baseAdapter.callBids - }); -}; - -ImproveDigitalAdapter.createNew = function () { - return new ImproveDigitalAdapter(); -}; - -adaptermanager.registerBidAdapter(new ImproveDigitalAdapter(), IMPROVE_DIGITAL_BIDDER_CODE); - -module.exports = ImproveDigitalAdapter; + if (keyValues) { + normalizedBidRequest.keyValues = keyValues; + } + if (localSize && localSize.w && localSize.h) { + normalizedBidRequest.size = {}; + normalizedBidRequest.size.h = localSize.h; + normalizedBidRequest.size.w = localSize.w; + } + if (bidId) { + normalizedBidRequest.id = bidId; + } + if (adUnitId) { + normalizedBidRequest.adUnitId = adUnitId; + } + if (transactionId) { + normalizedBidRequest.transactionId = transactionId; + } + return normalizedBidRequest; +} +registerBidder(spec); function ImproveDigitalAdServerJSClient(endPoint) { this.CONSTANTS = { @@ -184,13 +158,17 @@ function ImproveDigitalAdServerJSClient(endPoint) { }, AD_SERVER_BASE_URL: 'ad.360yield.com', END_POINT: endPoint || 'hb', - AD_SERVER_URL_PARAM: '?jsonp=', - CLIENT_VERSION: 'JS-4.0.2', + AD_SERVER_URL_PARAM: 'jsonp=', + CLIENT_VERSION: 'JS-4.2.0', MAX_URL_LENGTH: 2083, ERROR_CODES: { BAD_HTTP_REQUEST_TYPE_PARAM: 1, MISSING_PLACEMENT_PARAMS: 2, LIB_VERSION_MISSING: 3 + }, + RETURN_OBJ_TYPE: { + DEFAULT: 0, + PREBID: 1 } }; @@ -210,6 +188,8 @@ function ImproveDigitalAdServerJSClient(endPoint) { return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING); } + requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT; + let impressionObjects = []; let impressionObject; let counter; @@ -223,12 +203,19 @@ function ImproveDigitalAdServerJSClient(endPoint) { impressionObjects.push(impressionObject); } + let returnIdMappings = true; + if (requestParameters.returnObjType === this.CONSTANTS.RETURN_OBJ_TYPE.PREBID) { + returnIdMappings = false; + } + let returnObject = {}; - returnObject.idMappings = []; returnObject.requests = []; + if (returnIdMappings) { + returnObject.idMappings = []; + } let errors = null; - let baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + let baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; let bidRequestObject = { bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) @@ -243,49 +230,39 @@ function ImproveDigitalAdServerJSClient(endPoint) { adUnitId: impressionObject.adUnitId }); } else { - returnObject.idMappings.push({ - adUnitId: impressionObject.adUnitId, - id: impressionObject.impressionObject.id - }); - bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; - - bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); - let outputUri = encodeURIComponent(baseUrl + JSON.stringify(bidRequestObject)); - - if (!requestParameters.singleRequestMode) { - returnObject.requests.push({ - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + if (returnIdMappings) { + returnObject.idMappings.push({ + adUnitId: impressionObject.adUnitId, + id: impressionObject.impressionObject.id }); - bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; } + bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; + bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); + let writeLongRequest = false; + const outputUri = baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)); if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) { + writeLongRequest = true; if (bidRequestObject.bid_request.imp.length > 1) { + // Pop the current request and process it again in the next iteration bidRequestObject.bid_request.imp.pop(); - returnObject.requests.push({ - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) - }); - bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - bidRequestObject.bid_request.imp = []; - bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); - } else { - // We have a problem. Single request is too long for a URI + if (returnIdMappings) { + returnObject.idMappings.pop(); + } + counter--; } } + + if (writeLongRequest || + !requestParameters.singleRequestMode || + counter === impressionObjects.length - 1) { + returnObject.requests.push(this.formatRequest(requestParameters, bidRequestObject)); + bidRequestObject = { + bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) + }; + } } } - if (bidRequestObject.bid_request && - bidRequestObject.bid_request.imp && - bidRequestObject.bid_request.imp.length > 0) { - returnObject.requests = returnObject.requests || []; - returnObject.requests.push({ - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) - }); - } if (errors) { returnObject.errors = errors; @@ -294,6 +271,24 @@ function ImproveDigitalAdServerJSClient(endPoint) { return returnObject; }; + this.formatRequest = function(requestParameters, bidRequestObject) { + switch (requestParameters.returnObjType) { + case this.CONSTANTS.RETURN_OBJ_TYPE.PREBID: + return { + method: 'GET', + url: `//${this.CONSTANTS.AD_SERVER_BASE_URL}/${this.CONSTANTS.END_POINT}`, + data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${JSON.stringify(bidRequestObject)}` + }; + default: + const baseUrl = `${(requestParameters.secure === 1 ? 'https' : 'http')}://` + + `${this.CONSTANTS.AD_SERVER_BASE_URL}/` + + `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + return { + url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + } + } + }; + this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { let impressionBidRequestObject = {}; if (requestParameters.requestId) { diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md new file mode 100644 index 00000000000..3d91d4f82f2 --- /dev/null +++ b/modules/improvedigitalBidAdapter.md @@ -0,0 +1,47 @@ +# Overview + +**Module Name**: Improve Digital Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: hb@improvedigital.com + +# Description + +Module that connects to Improve Digital's demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'div-gpt-ad-1499748733608-0', + bids: [ + { + bidder: 'improvedigital', + params: { + placementId:1053688 + } + } + ] + }, { + code: 'div-gpt-ad-1499748833901-0', + bids: [{ + bidder: 'improvedigital', + params: { + placementId:1053689, + keyValues: { + testKey: ["testValue"] + } + } + }] + }, { + code: 'div-gpt-ad-1499748913322-0', + bids: [{ + bidder: 'improvedigital', + params: { + placementId:1053687, + size: { + w:300, + h:300 + } + } + }] + }]; +``` \ No newline at end of file diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5b0a9d37d57..750eecc2a7d 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,599 +1,313 @@ -describe('improvedigital adapter tests', function () { - const expect = require('chai').expect; - const Adapter = require('modules/improvedigitalBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const constants = require('src/constants.json'); - var bidfactory = require('src/bidfactory'); - var utils = require('src/utils.js'); - - var improveDigitalAdapter, - sandbox, - bidsRequestedOriginal; +import { expect } from 'chai'; +import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter'; +import { userSync } from 'src/userSync'; + +describe('Improve Digital Adapter Tests', function () { + let idClient = new ImproveDigitalAdServerJSClient('hb'); + + const METHOD = 'GET'; + const URL = '//ad.360yield.com/hb'; + const PARAM_PREFIX = 'jsonp='; const simpleBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012544 - } - } - ] + bidder: 'improvedigital', + params: { + placementId: 1053688 + }, + adUnitCode: 'div-gpt-ad-1499748733608-0', + transactionId: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + bidId: '33e9500b21129f', + bidderRequestId: '2772c1e566670b', + auctionId: '192721e36a0239' }; const simpleSmartTagBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - publisherId: 1032, - placementKey: 'data_team_test_hb_smoke_test' - } - } - ] + bidder: 'improvedigital', + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + publisherId: 1032, + placementKey: 'data_team_test_hb_smoke_test' + } }; - const keyValueBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012546, - keyValues: { - hbkv: ['01'] - } - } - } - ] - }; + describe('isBidRequestValid', () => { + it('should return false when no bid', () => { + expect(spec.isBidRequestValid()).to.equal(false); + }); - const sizeBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } - } - } - ] - }; + it('should return false when no bid.params', () => { + let bid = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const twoAdSlots = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012544, - } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } - } - } - ] - }; + it('should return false when both placementId and placementKey + publisherId are missing', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const threeAdSlots = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', + it('should return false when only one of placementKey and publisherId is present', () => { + let bid = { params: { - placementId: 1012544, + publisherId: 1234 } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid = { params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } + placementKey: 'xyz' } - }, - { - bidId: '7g8h9i', - placementCode: 'placement3', - params: { - placementId: 1012546, - keyValues: { - hbkv: ['01'] - } - } - } - ] - }; + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const badRequest1 = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - unknownId: 123456 - } - } - ] - }; + it('should return true when placementId is passed', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(simpleBidRequest)).to.equal(true); + }); - const twoAdSlotsSingleRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - singleRequest: true, - placementId: 1012544, - } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } + it('should return true when both placementKey and publisherId are passed', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(simpleSmartTagBidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should make a well-formed request objects', () => { + const requests = spec.buildRequests([simpleBidRequest]); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(1); + + const request = requests[0]; + expect(request.method).to.equal(METHOD); + expect(request.url).to.equal(URL); + expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); + + const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + expect(params.bid_request).to.be.an('object'); + expect(params.bid_request.id).to.be.a('string'); + expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); + expect(params.bid_request.imp).to.deep.equal([ + { + id: '33e9500b21129f', + pid: 1053688, + tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + banner: {} } - } - ] - }; + ]); + }); - const simpleResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + const requests = spec.buildRequests([simpleSmartTagBidRequest]); + const params = JSON.parse(requests[0].data.substring(PARAM_PREFIX.length)); + expect(params.bid_request.imp[0].pubid).to.equal(1032); + expect(params.bid_request.imp[0].pkey).to.equal('data_team_test_hb_smoke_test'); + }); - const zeroPriceResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 0, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + let bidRequest = Object.assign({}, simpleBidRequest); + const keyValues = { + testKey: [ + 'testValue' + ] + }; + bidRequest.params.keyValues = keyValues; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); + }); - const multipleResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + let bidRequest = Object.assign({}, simpleBidRequest); + const size = { w: 800, - cid: '99005', - adm: 'document.writeln(\" { + const requests = spec.buildRequests([ + simpleBidRequest, + simpleSmartTagBidRequest + ]); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(2); + }); + }); + + describe('interpretResponse', () => { + const serverResponse = { + 'body': { + 'id': '687a06c541d8d1', + 'site_id': 191642, + 'bid': [ + { + 'isNet': false, + 'id': '33e9500b21129f', + 'advid': '5279', + 'price': 1.45888594164456, + 'nurl': 'http://ad.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', + 'h': 290, + 'pid': 1053688, + 'sync': [ + 'http://link1', + 'http://link2' + ], + 'crid': '422031', + 'w': 600, + 'cid': '99006', + 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + } ], - nurl: 'http://nurl2', - h: 600, - pid: 1053687, - crid: '422030', - w: 800, - cid: '99005', - adm: 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + } ], - nurl: 'http://nurl2', - h: 600, - pid: 1053687, - crid: '422030', - w: 800, - cid: '99005' + 'debug': '' } - ], - debug: '' - }; + }; - const simpleResponseNoSync = { - id: '701903620', - site_id: 191642, - bid: [ + let expectedBid = [ { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\"', + 'adId': '33e9500b21129f', + 'creativeId': '422031', + 'cpm': 1.45888594164456, + 'currency': 'USD', + 'height': 290, + 'netRevenue': false, + 'requestId': '33e9500b21129f', + 'width': 600 } - ] - }; + ]; - var randomNumber = 9876543210; - beforeEach(() => { - improveDigitalAdapter = new Adapter(); - sandbox = sinon.sandbox.create(); - sandbox.stub( - utils, - 'getUniqueIdentifierStr', - function() { - var retValue = randomNumber.toString(); - randomNumber++; - return retValue; + let expectedTwoBids = [ + expectedBid[0], + { + 'ad': '', + 'adId': '1234', + 'creativeId': '422033', + 'cpm': 1.23, + 'currency': 'USD', + 'height': 400, + 'netRevenue': true, + 'requestId': '1234', + 'width': 700 } - ); - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); + ]; - afterEach(() => { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids simpleBidRequest', () => { - beforeEach(() => { - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(simpleBidRequest); + it('should return a well-formed bid', () => { + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.deep.equal(expectedBid); }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - }); - }); - describe('callBids simpleSmartTagBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(simpleSmartTagBidRequest); + it('should return two bids', () => { + const bids = spec.interpretResponse(serverResponseTwoBids); + expect(bids).to.deep.equal(expectedTwoBids); }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pubid%22%3A1032%2C%22pkey%22%3A%22data_team_test_hb_smoke_test%22%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - }); - }); - describe('callBids keyValueBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(keyValueBidRequest); - }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + it('should register user syncs', () => { + const registerSyncSpy = sinon.spy(userSync, 'registerSync'); + const bids = spec.interpretResponse(serverResponse); + expect(registerSyncSpy.withArgs('image', 'improvedigital', 'http://link1').calledOnce).to.equal(true); + expect(registerSyncSpy.withArgs('image', 'improvedigital', 'http://link2').calledOnce).to.equal(true); }); - }); - describe('callBids sizeBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(sizeBidRequest); - }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); + it('should set dealId correctly', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + let bids; - describe('callBids twoAdSlots', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(twoAdSlots); - }); - it('should call loadScript twice with correct parameters', () => { - sinon.assert.calledTwice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); + response.body.bid[0].lid = 'xyz'; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.not.exist; - describe('callBids threeAdSlots', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(threeAdSlots); - }); - it('should call loadScript thrice with correct parameters', () => { - sinon.assert.calledThrice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543212%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%227g8h9i%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - }); - }); + response.body.bid[0].lid = 268515; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.equal(268515); - describe('callBids bad request 1', () => { - beforeEach(() => { - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(badRequest1); + response.body.bid[0].lid = { + 1: 268515 + }; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.equal(268515); }); - it('should not call loadScript', () => { - sinon.assert.notCalled(adloader.loadScript); - }); - }); - describe('callBids twoAdSlotsSingleRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(twoAdSlotsSingleRequest); + it('should set currency', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].currency = 'eur'; + const bids = spec.interpretResponse(response); + expect(bids[0].currency).to.equal('EUR'); }); - it('should call loadScript twice with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%2C%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); - describe('improveDigitalResponse no response', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse([]); - }); - it('should not call bidmanager.addBidResponse', () => { - sinon.assert.notCalled(bidmanager.addBidResponse); - }); - }); - - describe('improveDigitalResponse simpleResponse', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponse); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); - }); - }); + it('should return empty array for bad response or no price', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + let bids; - describe('improveDigitalResponse zero bid', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(zeroPriceResponse); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, statusMessage: 'Bid returned empty or error response', adId: '1a2b3c'})); - }); - }); + // Price missing or 0 + response.body.bid[0].price = 0; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + delete response.body.bid[0].price; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + response.body.bid[0].price = null; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); - describe('improveDigitalResponse multipleResponseWithOneNoBid', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); - improveDigitalAdapter.callBids(twoAdSlots); - $$PREBID_GLOBAL$$.improveDigitalResponse(multipleResponseWithOneNoBid); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, adId: '1a2b3c', statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185})); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); - }); - }); + // errorCode present + response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].errorCode = undefined; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); - describe('improveDigitalResponse multipleInvalidResponses', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); - improveDigitalAdapter.callBids(twoAdSlots); - $$PREBID_GLOBAL$$.improveDigitalResponse(multipleInvalidResponses); - }); - it('should call bidmanager.addBidResponse twice both with invalid', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '1a2b3c', statusMessage: 'Bid returned empty or error response'})); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); + // Adm missing or bad + response = JSON.parse(JSON.stringify(serverResponse)); + delete response.body.bid[0].adm; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + response.body.bid[0].adm = null; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + response.body.bid[0].adm = 1234; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + response.body.bid[0].adm = {}; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); }); - }); - describe('improveDigitalResponse simpleResponseNoSync', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponseNoSync); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); + it('should set netRevenue', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].isNet = true; + const bids = spec.interpretResponse(response); + expect(bids[0].netRevenue).to.equal(true); }); }); }); From 6a0c5cabdbbbe1a45234c84fad33fcb794b13a92 Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Thu, 26 Oct 2017 19:02:59 +0300 Subject: [PATCH 21/62] Migrating TrustX adapter to 1.0 (#1709) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file --- modules/trustxBidAdapter.js | 287 ++++++------- modules/trustxBidAdapter.md | 40 ++ test/spec/modules/trustxBidAdapter_spec.js | 469 ++++++++++++--------- 3 files changed, 437 insertions(+), 359 deletions(-) create mode 100755 modules/trustxBidAdapter.md diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 13f893a841d..f16b8b96ec8 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,165 +1,148 @@ -const utils = require('src/utils.js'); -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const adloader = require('src/adloader'); -const adaptermanager = require('src/adaptermanager'); -const CONSTANTS = require('src/constants.json'); - -var TrustxAdapter = function TrustxAdapter() { - const bidderCode = 'trustx'; - const reqHost = '//sofia.trustx.org'; - const reqPath = '/hb?'; - const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', - noAdm: 'Bid from response has no adm parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find placementCode for bid with auid - ', - havePCodeFor: ', placementCode is available only for the following uids - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' - }; +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'trustx'; +const ENDPOINT_URL = '//sofia.trustx.org/hb'; +const TIME_TO_LIVE = 360; +const ADAPTER_SYNC_URL = '//sofia.trustx.org/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; +export const spec = { + code: BIDDER_CODE, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + /** + * 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 auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + }); - function _makeHandler(auids, placementMap) { - var cbName = bidderCode + '_callback_wrapper_' + auids.join('_'); - $$PREBID_GLOBAL$$[cbName] = function(resp) { - delete $$PREBID_GLOBAL$$[cbName]; - _responseProcessing(resp, auids, placementMap); + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), }; - return '$$PREBID_GLOBAL$$.' + cbName; - } - function _sendRequest(auids, placementMap) { - var query = []; - var path = reqPath; - query.push('u=' + encodeURIComponent(location.href)); - query.push('auids=' + encodeURIComponent(auids.join(','))); - query.push('cb=' + _makeHandler(auids, placementMap)); - query.push('pt=' + (window.globalPrebidTrustxPriceType === 'gross' ? 'gross' : 'net')); - - adloader.loadScript(reqHost + path + query.join('&')); - } - - function _callBids(params) { - var auids = []; - var placementMap = {}; - var hasBid; - var bid; - var bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - bid = bids[i]; - if (bid && bid.bidder === bidderCode && bid.placementCode) { - hasBid = true; - if (bid.params && bid.params.uid) { - if (!placementMap[bid.params.uid]) { - placementMap[bid.params.uid] = [bid.placementCode]; - auids.push(bid.params.uid); - } else { - placementMap[bid.params.uid].push(bid.placementCode); - } - } - } + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidsMap: bidsMap, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; } - if (auids.length) { - _sendRequest(auids, placementMap); - } else if (hasBid) { - utils.logError(LOG_ERROR_MESS.emptyUids); + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); } - } - - function _getBidFromResponse(resp) { - if (!resp) { - utils.logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!resp.bid) { - utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(resp)); - } else if (!resp.bid[0]) { - utils.logError(LOG_ERROR_MESS.noBid); + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; } - return resp && resp.bid && resp.bid[0]; } - - function _forEachPlacement(error, bid, placementCode) { - var bidObject; - if (error) { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bid); - } else { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bid); - bidObject.cpm = bid.price; - bidObject.ad = bid.adm; - bidObject.width = bid.w; - bidObject.height = bid.h; - if (bid.dealid) { - bidObject.dealId = bid.dealid; - } - } - bidObject.bidderCode = bidderCode; - bidmanager.addBidResponse(placementCode, bidObject); +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); } - - function _addBidResponse(bid, auids, placementMap) { - if (!bid) return; - var errorMessage, placementCodes; - if (!bid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(bid); - else { - placementCodes = placementMap.hasOwnProperty(bid.auid) && placementMap[bid.auid]; - if (!placementCodes) { - errorMessage = LOG_ERROR_MESS.noPlacementCode + bid.auid + LOG_ERROR_MESS.havePCodeFor + auids.join(','); - } - } - - if (!errorMessage) { - if (!bid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(bid); - - var l = placementCodes.length; - while (l--) { - _forEachPlacement(errorMessage, bid, placementCodes[l]); - } - - delete placementMap[bid.auid]; - } - - if (errorMessage) { - utils.logError(errorMessage); + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; } } - - function _responseProcessing(resp, auids, placementMap) { - var errorMessage; - - if (!resp) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (resp.seatbid && !resp.seatbid.length) errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - - if (!errorMessage) { - resp = resp.seatbid || []; - var l = resp.length; - while (l--) { - _addBidResponse(_getBidFromResponse(resp[l]), auids, placementMap); - } - } - - var n, bidObj; - for (var auid in placementMap) { - if (placementMap.hasOwnProperty(auid) && placementMap[auid]) { - n = placementMap[auid].length; - while (n--) { - bidObj = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); - bidObj.bidderCode = bidderCode; - bidmanager.addBidResponse(placementMap[auid][n], bidObj); - } - } - } - - if (errorMessage) utils.logError(errorMessage); + if (errorMessage) { + utils.logError(errorMessage); } +} - return { - callBids: _callBids - }; -}; - -adaptermanager.registerBidAdapter(new TrustxAdapter(), 'trustx'); - -module.exports = TrustxAdapter; +registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md new file mode 100755 index 00000000000..ca407b0c5e8 --- /dev/null +++ b/modules/trustxBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: TrustX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: paul@trustx.org + +# Description + +Module that connects to TrustX demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "trustx", + params: { + uid: '44', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "trustx", + params: { + uid: 45, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 7208ebef343..918e03674a9 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -1,234 +1,289 @@ -describe('trustx adapter tests', function () { - var expect = require('chai').expect; - var assert = require('chai').assert; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - - var adapter = require('modules/trustxBidAdapter'); - var bidmanager = require('src/bidmanager'); - var adLoader = require('src/adloader'); - var utils = require('src/utils'); - window.$$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$ || {}; - - if (typeof (pbjs) === 'undefined') { - var pbjs = window.$$PREBID_GLOBAL$$; - } - let stubLoadScript; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - afterEach(function () { - stubLoadScript.restore(); - }); - var logErrorSpy; - beforeEach(function () { - logErrorSpy = sinon.spy(utils, 'logError'); - }); - afterEach(function () { - logErrorSpy.restore(); +import { expect } from 'chai'; +import { spec } from 'modules/trustxBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TrustXAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('creation of request url', function () { - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - it('should fix parameter name', function () { - var params = { - bidderCode: 'trustx', - bids: [ - { - bidder: 'trustx', - params: { - uid: 5 - }, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - params: { - uid: 6 - }, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - params: {}, - placementCode: 'div-2' - }, - { - bidder: 'trustx', - params: { - uid: 6, - test: true - }, - placementCode: 'div-3' - }, - { - bidder: 'trustx', - placementCode: 'div-4' - } - ] + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'trustx', + 'params': { + 'uid': '44' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 }; - adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - sinon.assert.calledWith(stubLoadScript, bidUrl); - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - var generatedCallback = '$$PREBID_GLOBAL$$.trustx_callback_wrapper_5_6'; - expect(parsedBidUrl.hostname).to.equal('sofia.trustx.org'); - expect(parsedBidUrl.pathname).to.equal('/hb'); - expect(parsedBidUrlQueryString).to.have.property('auids').and.to.equal('5,6'); - expect(parsedBidUrlQueryString).to.have.property('u').and.to.equal(location.href); - expect(parsedBidUrlQueryString).to.have.property('cb').and.to.equal(generatedCallback); + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('validate incoming params', function () { - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - it('has no correct item in config', function () { - var params = { - bidderCode: 'trustx', - bids: [ - { - bidder: 'trustx', - params: {}, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - placementCode: 'div-1' - } - ] - }; - adapter().callBids(params); - sinon.assert.notCalled(stubLoadScript); - expect(logErrorSpy.getCall(0).args[0]).to.equal('Uids should be not empty'); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '45' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '43'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '43,45'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '43,45'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '43,45'); + delete bidRequests[1].params.priceType; }); }); - describe('handling of the callback response', function () { - if (typeof (pbjs._bidsReceived) === 'undefined') { - pbjs._bidsReceived = []; - } - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - if (typeof (pbjs._adsReceived) === 'undefined') { - pbjs._adsReceived = []; - } - var params = { - bidderCode: 'trustx', - bids: [ + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 44, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 45, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', () => { + const bidRequests = [ { - bidder: 'trustx', - params: { - uid: 5 + 'bidder': 'trustx', + 'params': { + 'uid': '43' }, - placementCode: '/19968336/header-bid-tag-0' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', }, { - bidder: 'trustx', - params: { - uid: 6 + 'bidder': 'trustx', + 'params': { + 'uid': '44' }, - placementCode: '/19968336/header-bid-tag-1' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', }, { - bidder: 'trustx', - params: { - uid: 42 + 'bidder': 'trustx', + 'params': { + 'uid': '43' }, - placementCode: '/19968336/header-bid-tag-2' + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, }, { - bidder: 'trustx', - params: { - uid: 43 + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 44, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '45' }, - placementCode: '/19968336/header-bid-tag-3' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', }, { - bidder: 'trustx', - params: { - uid: 44 + 'bidder': 'trustx', + 'params': { + 'uid': '46' }, - placementCode: '/19968336/header-bid-tag-4' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', }, { - bidder: 'trustx', - params: { - uid: 45 + 'bidder': 'trustx', + 'params': { + 'uid': '50' }, - placementCode: '/19968336/header-bid-tag-5' + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', } - ] - }; - it('callback function should exist', function () { - adapter().callBids(params); - expect(pbjs['trustx_callback_wrapper_5_6_42_43_44_45']) - .to.exist.and.to.be.a('function'); - }); - it('bidmanager.addBidResponse should be called with correct arguments', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - adapter().callBids(params); - var adUnits = []; - var unit = {}; - unit.bids = params.bids; - unit.code = '/19968336/header-bid-tag'; - adUnits.push(unit); - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = [params]; - } else { - pbjs._bidsRequested.push(params); - } - pbjs.adUnits = adUnits; - var response = { - seatbid: [ - {bid: [{price: 1.15, adm: '
test content 1
', auid: 5, h: 90, w: 728}], seat: '1'}, - {bid: [{price: 0, auid: 6, h: 250, w: 300}], seat: '1'}, - {bid: [{price: 0, adm: '
test content 3
', h: 250, w: 300}], seat: '1'}, - undefined, - {bid: [], seat: '1'}, - {seat: '1'}, - {bid: [{price: 0, adm: '
test content 7
', auid: 46, h: 250, w: 300}], seat: '1'} - ] - }; - pbjs['trustx_callback_wrapper_5_6_42_43_44_45'](response); - var bidPlacementCode1 = stubAddBidResponse.getCall(1).args[0]; - var bidObject1 = stubAddBidResponse.getCall(1).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(0).args[0]; - var bidObject2 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - var bidObject3 = stubAddBidResponse.getCall(2).args[1]; - var bidPlacementCode4 = stubAddBidResponse.getCall(3).args[0]; - var bidObject4 = stubAddBidResponse.getCall(3).args[1]; - var bidPlacementCode5 = stubAddBidResponse.getCall(4).args[0]; - var bidObject5 = stubAddBidResponse.getCall(4).args[1]; - var bidPlacementCode6 = stubAddBidResponse.getCall(5).args[0]; - var bidObject6 = stubAddBidResponse.getCall(5).args[1]; - expect(logErrorSpy.getCall(5).args[0]).to.equal('Bid from response has no adm parameter - {"price":0,"auid":6,"h":250,"w":300}'); - expect(logErrorSpy.getCall(4).args[0]).to.equal('Bid from response has no auid parameter - {"price":0,"adm":"<' + 'div>test content 3","h":250,"w":300}'); - expect(logErrorSpy.getCall(3).args[0]).to.equal('Seatbid array from response has empty item'); - expect(logErrorSpy.getCall(2).args[0]).to.equal('Array of bid objects is empty'); - expect(logErrorSpy.getCall(1).args[0]).to.equal('Seatbid from response has no array of bid objects - {"seat":"1"}'); - expect(logErrorSpy.getCall(0).args[0]).to.equal('Can\'t find placementCode for bid with auid - 46, placementCode is available only for the following uids - 5,6,42,43,44,45'); - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(1.15); - expect(bidObject1.ad).to.equal('
test content 1
'); - expect(bidObject1.width).to.equal(728); - expect(bidObject1.height).to.equal(90); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('trustx'); - expect(bidPlacementCode2).to.equal('/19968336/header-bid-tag-1'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidPlacementCode3).to.equal('/19968336/header-bid-tag-2'); - expect(bidObject3.getStatusCode()).to.equal(2); - expect(bidPlacementCode4).to.equal('/19968336/header-bid-tag-3'); - expect(bidObject4.getStatusCode()).to.equal(2); - expect(bidPlacementCode5).to.equal('/19968336/header-bid-tag-4'); - expect(bidObject5.getStatusCode()).to.equal(2); - expect(bidPlacementCode6).to.equal('/19968336/header-bid-tag-5'); - expect(bidObject6.getStatusCode()).to.equal(2); - stubAddBidResponse.restore(); + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); }); }); }); From caec5c0022405fada40562e6483abc7f92856d6a Mon Sep 17 00:00:00 2001 From: dbemiller Date: Thu, 26 Oct 2017 12:03:44 -0400 Subject: [PATCH 22/62] Fix test-coverage bug (#1765) * Fixed a bug in test-coverage single-file tests. * Reverted unintended changes. --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index d7a151ce357..7b05b22ad06 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -189,7 +189,7 @@ gulp.task('test', ['clean'], function (done) { // If --file "" is given, the task will only run tests in the specified file. gulp.task('test-coverage', ['clean'], function(done) { - new KarmaServer(karmaConfMaker(true, false, argv.file), newKarmaCallback(done)).start(); + new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); }); // View the code coverage report in the browser. From 9a6ddc28313d9a0a09e1153fd9465ee52bd0e6a3 Mon Sep 17 00:00:00 2001 From: Paul Yang Date: Thu, 26 Oct 2017 09:31:51 -0700 Subject: [PATCH 23/62] Update Conversant adapter to Prebid 1.0 (#1711) * Conversant adapter initial support for prebid 1.0 * Add video support for conversant adapter * Add tests and md * Update conversant contact address * Return data object in buildRequests without converting it to a string * Conversant adapter initial support for prebid 1.0 * Add video support for conversant adapter * Add tests and md * Update conversant contact address * Return data object in buildRequests without converting it to a string * Better validation for site id * Switch to use utils._each and utils._map * Add tests for displaymanagerver * Review changes for conversant --- modules/conversantBidAdapter.js | 458 +++++++------ modules/conversantBidAdapter.md | 41 ++ .../spec/modules/conversantBidAdapter_spec.js | 613 ++++++++---------- 3 files changed, 517 insertions(+), 595 deletions(-) create mode 100644 modules/conversantBidAdapter.md 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); }); -}); +}) From edc22740c37f0e0f5eaf0f28498ada6a8aa8d45d Mon Sep 17 00:00:00 2001 From: Jacek Drobiecki Date: Thu, 26 Oct 2017 18:38:40 +0200 Subject: [PATCH 24/62] Add AdOcean adapter (#1735) * Initial revision of adocean bid adapter (ADOCEAN-13634, ADOCEAN-13635) * Minor fixes * new demo placement * formating after lint * move request parameters to params * adocean adpater tests * minor fixes * added ttl, netRevenue nad creativeId. merged with upstream --- modules/adoceanBidAdapter.js | 101 +++++++++++++ modules/adoceanBidAdapter.md | 30 ++++ test/spec/modules/adoceanBidAdapter_spec.js | 158 ++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 modules/adoceanBidAdapter.js create mode 100644 modules/adoceanBidAdapter.md create mode 100644 test/spec/modules/adoceanBidAdapter_spec.js diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js new file mode 100644 index 00000000000..1949e0414a7 --- /dev/null +++ b/modules/adoceanBidAdapter.js @@ -0,0 +1,101 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'adocean'; + +function buildEndpointUrl(emiter, payload) { + let payloadString = ''; + utils._each(payload, function(v, k) { + if (payloadString.length) { + payloadString += '&'; + } + payloadString += k + '=' + encodeURIComponent(v); + }); + + return 'https://' + emiter + '/ad.json?' + payloadString; +} + +function buildRequest(masterBidRequests, masterId) { + const firstBid = masterBidRequests[0]; + const payload = { + id: masterId, + }; + + const bidIdMap = {}; + + utils._each(masterBidRequests, function(v) { + bidIdMap[v.params.slaveId] = v.bidId; + }); + + return { + method: 'GET', + url: buildEndpointUrl(firstBid.params.emiter, payload), + data: {}, + bidIdMap: bidIdMap + }; +} + +function assignToMaster(bidRequest, bidRequestsByMaster) { + const masterId = bidRequest.params.masterId; + bidRequestsByMaster[masterId] = bidRequestsByMaster[masterId] || []; + bidRequestsByMaster[masterId].push(bidRequest); +} + +function interpretResponse(placementResponse, bidRequest, bids) { + if (!placementResponse.error) { + let adCode = '\n\nQuantcast\n\n\n', - 'width': 300, - 'height': 250 + statusCode: 1, + placementCode: 'imp1', // Changing this to placementCode to be reflective + cpm: 4.5, + currency: 'USD', + ad: + '
Quantcast
', + creativeId: 1001, + width: 300, + height: 250 } ] }; - beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - addBidReponseStub = sandbox.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - }); + const response = { + body, + headers: {} + }; - afterEach(() => { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); + it('should return an empty array if `serverResponse` is `undefined`', () => { + const interpretedResponse = qcSpec.interpretResponse(); - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.handleQuantcastCB).to.exist.and.to.be.a('function'); + expect(interpretedResponse.length).to.equal(0); }); - it('should not add bid when empty text response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB(); - sinon.assert.notCalled(addBidReponseStub); - }); + it('should return an empty array if the parsed response does NOT include `bids`', () => { + const interpretedResponse = qcSpec.interpretResponse({}); - it('should not add bid when empty json response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify({})); - sinon.assert.notCalled(addBidReponseStub); + expect(interpretedResponse.length).to.equal(0); }); - it('should not add bid when malformed json response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB('non json text'); - sinon.assert.notCalled(addBidReponseStub); + it('should return an empty array if the parsed response has an empty `bids`', () => { + const interpretedResponse = qcSpec.interpretResponse({ bids: [] }); + + expect(interpretedResponse.length).to.equal(0); }); - it('should add a bid object for each bid', () => { - // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); - sinon.assert.calledOnce(addBidReponseStub); - expect(addBidReponseStub.firstCall.args[0]).to.eql('div-gpt-ad-1438287399331-0'); + it('should get correct bid response', () => { + const expectedResponse = { + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', + cpm: 4.5, + width: 300, + height: 250, + ad: + '
Quantcast
', + ttl: QUANTCAST_TTL, + creativeId: 1001, + netRevenue: QUANTCAST_NET_REVENUE, + currency: 'USD' + }; + const interpretedResponse = qcSpec.interpretResponse(response); + + expect(interpretedResponse[0]).to.deep.equal(expectedResponse); }); - it('should return no bid even when requestId and sizes are missing', () => { - let bidderReponse = { - 'bidderCode': 'quantcast', - 'bids': [ - { - 'statusCode': 0, - 'placementCode': bidderRequest.bids[0].bidId, - } - ] + it('handles no bid response', () => { + const body = { + bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', // Added this field. This is not used now but could be useful in troubleshooting later on. Specially for sites using iFrames + bids: [] + }; + const response = { + body, + headers: {} }; + const expectedResponse = []; + const interpretedResponse = qcSpec.interpretResponse(response); - // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); - // sinon.assert.calledOnce(addBidReponseStub); - // expect(addBidReponseStub.firstCall.args[0]).to.eql("div-gpt-ad-1438287399331-0"); + expect(interpretedResponse.length).to.equal(0); }); }); }); From 90c66f2ad535e580d78c9ae12c225f9bb1933b35 Mon Sep 17 00:00:00 2001 From: Connor Doherty Date: Thu, 26 Oct 2017 23:36:01 +0300 Subject: [PATCH 29/62] Update yieldmoBid adapter request url (#1771) --- modules/yieldmoBidAdapter.js | 2 +- test/spec/modules/yieldmoBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index d311bb5722c..73f18794ea7 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -19,7 +19,7 @@ var YieldmoAdapter = function YieldmoAdapter() { function buildYieldmoCall(bids) { // build our base tag, based on if we are http or https - var ymURI = '//bid.yieldmo.com/exchange/prebid?'; + var ymURI = '//ads.yieldmo.com/exchange/prebid?'; var ymCall = document.location.protocol + ymURI; // Placement specific information diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 370aeb15457..d2a533be17e 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -52,7 +52,7 @@ describe('Yieldmo adapter', () => { }); it('should load a script with passed bid params', () => { - let route = 'http://bid.yieldmo.com/exchange/prebid?'; + let route = 'http://ads.yieldmo.com/exchange/prebid?'; let requestParams = parseURL(bidRequestURL).search; let parsedPlacementParams = JSON.parse(decodeURIComponent(requestParams.p)); From d5f1cd108a722966aafb6932d94df9f77c41cd7d Mon Sep 17 00:00:00 2001 From: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Date: Thu, 26 Oct 2017 23:08:45 +0200 Subject: [PATCH 30/62] Update adxcg adapter for prebid 1.0 (#1741) * updated adxcg adapter for prebid 1.0 * update for prebid 1.0 with dead code and bidder removed * updates to spec file for prebid 1.0 - removal of biddercode check * updated adxcg bidadapter for prebid-1.0 with response headers --- modules/adxcgBidAdapter.js | 268 ++++++++------- modules/adxcgBidAdapter.md | 50 +++ test/spec/modules/adxcgBidAdapter_spec.js | 390 ++++++++++++---------- 3 files changed, 414 insertions(+), 294 deletions(-) create mode 100644 modules/adxcgBidAdapter.md diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 476cb5989e0..9073a17bda3 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,139 +1,155 @@ -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; -import { ajax } from 'src/ajax'; import * as url from 'src/url'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {NATIVE, VIDEO} from 'src/mediaTypes'; /** - * Adapter for requesting bids from Adxcg - * updated from latest prebid repo on 2017.08.30 + * Adapter for requesting bids from adxcg.net + * updated to latest prebid repo on 2017.10.20 */ -function AdxcgAdapter() { - let bidRequests = {}; - - function _callBids(params) { - if (params.bids && params.bids.length > 0) { - let adZoneIds = []; - let prebidBidIds = []; - let sizes = []; - - params.bids.forEach(bid => { - bidRequests[bid.bidId] = bid; - adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); - prebidBidIds.push(bid.bidId); - sizes.push(utils.parseSizesInput(bid.sizes).join('|')); - }); - - let location = utils.getTopWindowLocation(); - let secure = location.protocol == 'https:'; - - let requestUrl = url.parse(location.href); - requestUrl.search = null; - requestUrl.hash = null; - - let adxcgRequestUrl = url.format({ - protocol: secure ? 'https' : 'http', - hostname: secure ? 'ad-emea-secure.adxcg.net' : 'ad-emea.adxcg.net', - pathname: '/get/adi', - search: { - renderformat: 'javascript', - ver: 'r20141124', - adzoneid: adZoneIds.join(','), - format: sizes.join(','), - prebidBidIds: prebidBidIds.join(','), - url: escape(url.format(requestUrl)), - secure: secure ? '1' : '0' - } - }); - utils.logMessage(`submitting request: ${adxcgRequestUrl}`); - ajax(adxcgRequestUrl, handleResponse, null, { - withCredentials: true - }); - } - } - - function handleResponse(response) { - let adxcgBidReponseList; - - try { - adxcgBidReponseList = JSON.parse(response); - utils.logMessage(`adxcgBidReponseList: ${JSON.stringify(adxcgBidReponseList)}`); - } catch (error) { - adxcgBidReponseList = []; - utils.logError(error); - } +const BIDDER_CODE = 'adxcg'; +const SUPPORTED_AD_TYPES = [VIDEO, NATIVE]; +const SOURCE = 'pbjs10'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.adzoneid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * an array of validBidRequests + * Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + utils.logMessage(`buildRequests: ${JSON.stringify(validBidRequests)}`); + + let adZoneIds = []; + let prebidBidIds = []; + let sizes = []; + + validBidRequests.forEach(bid => { + adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)); + prebidBidIds.push(bid.bidId); + sizes.push(utils.parseSizesInput(bid.sizes).join('|')); + }); - adxcgBidReponseList.forEach(adxcgBidReponse => { - let bidRequest = bidRequests[adxcgBidReponse.bidId]; - delete bidRequests[adxcgBidReponse.bidId]; - - let bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - - bid.creative_id = adxcgBidReponse.creativeId; - bid.code = 'adxcg'; - bid.bidderCode = 'adxcg'; - bid.cpm = adxcgBidReponse.cpm; - - if (adxcgBidReponse.ad) { - bid.ad = adxcgBidReponse.ad; - } else if (adxcgBidReponse.vastUrl) { - bid.vastUrl = adxcgBidReponse.vastUrl; - bid.descriptionUrl = adxcgBidReponse.vastUrl; - bid.mediaType = 'video'; - } else if (adxcgBidReponse.nativeResponse) { - bid.mediaType = 'native'; - - let nativeResponse = adxcgBidReponse.nativeResponse; - - bid['native'] = { - clickUrl: escape(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers - }; - - nativeResponse.assets.forEach(asset => { - if (asset.title && asset.title.text) { - bid['native'].title = asset.title.text; - } - - if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url; - } - - if (asset.data && asset.data.label == 'DESC' && asset.data.value) { - bid['native'].body = asset.data.value; - } - - if (asset.data && asset.data.label == 'SPONSORED' && asset.data.value) { - bid['native'].sponsoredBy = asset.data.value; - } - }); + let location = utils.getTopWindowLocation(); + let secure = location.protocol === 'https:'; + + let requestUrl = url.parse(location.href); + requestUrl.search = null; + requestUrl.hash = null; + + let adxcgRequestUrl = url.format({ + protocol: secure ? 'https' : 'http', + hostname: secure ? 'hbps.adxcg.net' : 'hbp.adxcg.net', + pathname: '/get/adi', + search: { + renderformat: 'javascript', + ver: 'r20171019PB10', + adzoneid: adZoneIds.join(','), + format: sizes.join(','), + prebidBidIds: prebidBidIds.join(','), + url: encodeURIComponent(url.format(requestUrl)), + secure: secure ? '1' : '0', + source: SOURCE, + pbjs: '$prebid.version$' } + }); - bid.width = adxcgBidReponse.width; - bid.height = adxcgBidReponse.height; + return { + method: 'GET', + url: adxcgRequestUrl, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {bidRequests[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequests) { + let bids = []; + + serverResponse = serverResponse.body; + if (serverResponse) { + serverResponse.forEach(serverResponseOneItem => { + let bid = {}; + + bid.requestId = serverResponseOneItem.bidId; + bid.cpm = serverResponseOneItem.cpm; + bid.creativeId = parseInt(serverResponseOneItem.creativeId); + bid.currency = 'USD'; + bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true; + bid.ttl = 300; + + if (serverResponseOneItem.deal_id != null && serverResponseOneItem.deal_id.trim().length > 0) { + bid.dealId = serverResponseOneItem.deal_id; + } - utils.logMessage(`submitting bid[${bidRequest.placementCode}]: ${JSON.stringify(bid)}`); - bidmanager.addBidResponse(bidRequest.placementCode, bid); - }); + if (serverResponseOneItem.ad) { + bid.ad = serverResponseOneItem.ad; + } else if (serverResponseOneItem.vastUrl) { + bid.vastUrl = serverResponseOneItem.vastUrl; + bid.descriptionUrl = serverResponseOneItem.vastUrl; + bid.mediaType = 'video'; + } else if (serverResponseOneItem.nativeResponse) { + bid.mediaType = 'native'; + + let nativeResponse = serverResponseOneItem.nativeResponse; + + bid['native'] = { + clickUrl: encodeURIComponent(nativeResponse.link.url), + impressionTrackers: nativeResponse.imptrackers + }; + + nativeResponse.assets.forEach(asset => { + if (asset.title && asset.title.text) { + bid['native'].title = asset.title.text; + } + + if (asset.img && asset.img.url) { + bid['native'].image = asset.img.url; + } + + if (asset.data && asset.data.label === 'DESC' && asset.data.value) { + bid['native'].body = asset.data.value; + } + + if (asset.data && asset.data.label === 'SPONSORED' && asset.data.value) { + bid['native'].sponsoredBy = asset.data.value; + } + }); + } - Object.keys(bidRequests) - .map(bidId => bidRequests[bidId].placementCode) - .forEach(placementCode => { - utils.logMessage(`creating no_bid bid for: ${placementCode}`); - bidmanager.addBidResponse(placementCode, bidfactory.createBid(STATUS.NO_BID)); + bid.width = serverResponseOneItem.width; + bid.height = serverResponseOneItem.height; + utils.logMessage(`submitting bid[${serverResponseOneItem.bidId}]: ${JSON.stringify(bid)}`); + bids.push(bid); }); - }; - - return { - callBids: _callBids - }; + } else { + utils.logMessage(`empty bid response`); + } + return bids; + }, + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//cdn.adxcg.net/pb-sync.html' + }]; + } + } }; - -adaptermanager.registerBidAdapter(new AdxcgAdapter(), 'adxcg', { - supportedMediaTypes: ['video', 'native'] -}); - -module.exports = AdxcgAdapter; +registerBidder(spec); diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md new file mode 100644 index 00000000000..f3cd8c6d308 --- /dev/null +++ b/modules/adxcgBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +**Module Name**: Adxcg Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: info@adxcg.com + +# Description + +Module that connects to an Adxcg.com zone to fetch bids. + +# Test Parameters +``` + `` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '1' + } + }] + },{ + code: 'native-ad-div', + sizes: [[300, 250], [1, 1]], + nativeParams: { + title: { required: true, len: 75 }, + image: { required: true }, + body: { len: 200 }, + sponsoredBy: { len: 20 } + }, + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '2379' + } + } + }] + },{ + code: 'video', + sizes: [[640, 480]], + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '20' + } + } + }] + }]; +``` diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index fa55bf92e2e..dbf7359e98d 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,116 +1,62 @@ -import { expect } from 'chai'; -import Adapter from 'modules/adxcgBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {expect} from 'chai'; import * as url from 'src/url'; +import {spec} from 'modules/adxcgBidAdapter'; -const REQUEST = { - 'bidderCode': 'adxcg', - 'bids': [ - { +describe('AdxcgAdapter', () => { + describe('isBidRequestValid', () => { + let bid = { 'bidder': 'adxcg', 'params': { - 'adzoneid': '1', + 'adzoneid': '1' }, - 'sizes': [ - [300, 250], - [640, 360], - [1, 1] - ], + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2' - } - ] -}; - -const RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 300, - 'height': 250, - 'creativeId': '42', - 'cpm': 0.45, - 'ad': '' -}] - -const VIDEO_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 640, - 'height': 360, - 'creativeId': '42', - 'cpm': 0.45, - 'vastUrl': 'vastContentUrl' -}] - -const NATIVE_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 0, - 'height': 0, - 'creativeId': '42', - 'cpm': 0.45, - 'nativeResponse': { - 'assets': [{ - 'id': 1, - 'required': 0, - 'title': { - 'text': 'titleContent' - } - }, { - 'id': 2, - 'required': 0, - 'img': { - 'url': 'imageContent', - 'w': 600, - 'h': 600 - } - }, { - 'id': 3, - 'required': 0, - 'data': { - 'label': 'DESC', - 'value': 'descriptionContent' - } - }, { - 'id': 0, - 'required': 0, - 'data': { - 'label': 'SPONSORED', - 'value': 'sponsoredByContent' - } - }], - 'link': { - 'url': 'linkContent' - }, - 'imptrackers': ['impressionTracker1', 'impressionTracker2'] - } -}] + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; -describe('AdxcgAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); + }); - afterEach(() => xhr.restore()); + describe('request function http', () => { + let bid = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; it('creates a valid adxcg request url', () => { - adapter.callBids(REQUEST); + let request = spec.buildRequests([bid]); + expect(request).to.exist; + // console.log('IS:' + JSON.stringify(request)); - let parsedRequestUrl = url.parse(requests[0].url); + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); - expect(parsedRequestUrl.hostname).to.equal('ad-emea.adxcg.net'); + expect(parsedRequestUrl.hostname).to.equal('hbp.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20141124'); + expect(query.ver).to.equal('r20171019PB10'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('1'); expect(query.format).to.equal('300x250|640x360|1x1'); expect(query.jsonp).to.be.empty; @@ -119,94 +65,202 @@ describe('AdxcgAdapter', () => { }); describe('response handler', () => { - let server; + let BIDDER_REQUEST = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + let BANNER_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } - afterEach(() => { - server.restore() - bidmanager.addBidResponse.restore(); - }); + let BANNER_RESPONSE_WITHDEALID = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'deal_id': '7722', + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } + + let VIDEO_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 640, + 'height': 360, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'vastContentUrl' + }], + header: {'someheader': 'fakedata'} + } + + let NATIVE_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 0, + 'height': 0, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'nativeResponse': { + 'assets': [{ + 'id': 1, + 'required': 0, + 'title': { + 'text': 'titleContent' + } + }, { + 'id': 2, + 'required': 0, + 'img': { + 'url': 'imageContent', + 'w': 600, + 'h': 600 + } + }, { + 'id': 3, + 'required': 0, + 'data': { + 'label': 'DESC', + 'value': 'descriptionContent' + } + }, { + 'id': 0, + 'required': 0, + 'data': { + 'label': 'SPONSORED', + 'value': 'sponsoredByContent' + } + }], + 'link': { + 'url': 'linkContent' + }, + 'imptrackers': ['impressionTracker1', 'impressionTracker2'] + } + }], + header: {'someheader': 'fakedata'} + } it('handles regular responses', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('banner'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.ad).to.equal(''); + let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + }); + + it('handles regular responses with dealid', () => { + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles video responses', () => { - server.respondWith(JSON.stringify(VIDEO_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(640); - expect(bidResponse.height).to.equal(360); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.vastUrl).to.equal('vastContentUrl'); - expect(bidResponse.descriptionUrl).to.equal('vastContentUrl'); + let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(360); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].vastUrl).to.equal('vastContentUrl'); + expect(result[0].descriptionUrl).to.equal('vastContentUrl'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles native responses', () => { - server.respondWith(JSON.stringify(NATIVE_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(0); - expect(bidResponse.height).to.equal(0); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('native'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - - expect(bidResponse.native.clickUrl).to.equal('linkContent'); - expect(bidResponse.native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); - expect(bidResponse.native.title).to.equal('titleContent'); - expect(bidResponse.native.image).to.equal('imageContent'); - expect(bidResponse.native.body).to.equal('descriptionContent'); - expect(bidResponse.native.sponsoredBy).to.equal('sponsoredByContent'); + let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + + expect(result[0].width).to.equal(0); + expect(result[0].height).to.equal(0); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + + expect(result[0].native.clickUrl).to.equal('linkContent'); + expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); + expect(result[0].native.title).to.equal('titleContent'); + expect(result[0].native.image).to.equal('imageContent'); + expect(result[0].native.body).to.equal('descriptionContent'); + expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); }); it('handles nobid responses', () => { - server.respondWith('[]'); + let response = []; + let bidderRequest = BIDDER_REQUEST; + + let result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); + }); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + describe('getUserSyncs', () => { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + }; - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.statusMessage).to.equal('Bid returned empty or error response'); + it('should return iframe sync option', () => { + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('//cdn.adxcg.net/pb-sync.html'); }); }); }); From c2dd4c771dcb7bdec12e46302dad752f48c1ddd1 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Fri, 27 Oct 2017 09:54:44 -0400 Subject: [PATCH 31/62] add vastUrl + media type for video bids Prebid Server (#1739) * add vastUrl + media type for video bids * updates per review * updates per review * updates per review --- modules/prebidServerBidAdapter.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index 0906a1a0b3d..89a59fa7c67 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -7,6 +7,7 @@ import { STATUS, S2S } from 'src/constants'; import { cookieSet } from 'src/cookie.js'; import adaptermanager from 'src/adaptermanager'; import { config } from 'src/config'; +import { VIDEO } from 'src/mediaTypes'; const getConfig = config.getConfig; @@ -111,7 +112,9 @@ function PrebidServer() { if (videoMediaType) { // pbs expects a ad_unit.video attribute if the imp is video adUnit.video = Object.assign({}, videoMediaType); - delete adUnit.mediaTypes.video; + delete adUnit.mediaTypes; + // default is assumed to be 'banner' so if there is a video type we assume video only until PBS can support multi format auction. + adUnit.media_types = [VIDEO]; } }) convertTypes(adUnits); @@ -196,10 +199,26 @@ function PrebidServer() { bidObject.creative_id = bidObj.creative_id; bidObject.bidderCode = bidObj.bidder; bidObject.cpm = cpm; - bidObject.ad = bidObj.adm; - if (bidObj.nurl) { - bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bidObj.nurl)); + // From ORTB see section 4.2.3: adm Optional means of conveying ad markup in case the bid wins; supersedes the win notice if markup is included in both. + if (bidObj.media_type === VIDEO) { + bidObject.mediaType = VIDEO; + if (bidObj.adm) { + bidObject.vastXml = bidObj.adm; + } + if (bidObj.nurl) { + bidObject.vastUrl = bidObj.nurl; + } + } else { + if (bidObj.adm && bidObj.nurl) { + bidObject.ad = bidObj.adm; + bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bidObj.nurl)); + } else if (bidObj.adm) { + bidObject.ad = bidObj.adm; + } else if (bidObj.nurl) { + bidObject.adUrl = bidObj.nurl + } } + bidObject.width = bidObj.width; bidObject.height = bidObj.height; bidObject.adserverTargeting = bidObj.ad_server_targeting; @@ -223,7 +242,6 @@ function PrebidServer() { bidObject.source = TYPE; bidObject.adUnitCode = bidRequest.placementCode; bidObject.bidderCode = bidRequest.bidder; - bidmanager.addBidResponse(bidObject.adUnitCode, bidObject); }); }); From a592a7a003ebee8e75342dfb27cf7dc5a0cfee29 Mon Sep 17 00:00:00 2001 From: Niksok Date: Fri, 27 Oct 2017 20:48:42 +0300 Subject: [PATCH 32/62] Fix Centro adapter to allow requests of the same units (#1746) * Add centro adapter and tests for it. * fix bug with different types of bid.sectionID and bid.unit from config * add query parameter adapter=prebid * update tests for centro adapter * fixed bug with call of JSONP callback with name, that contain invalid characters * Centro adapter fix: do not call logError if 'No Bid' was received * Centro adapter: pass the bid request object to bidfactory.createBid * Centro adapter: fix ESLintError * Fix Centro adapter to allow requests of the same units * Fix spec file for Centro adapter --- modules/centroBidAdapter.js | 2 +- test/spec/modules/centroBidAdapter_spec.js | 23 ++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/centroBidAdapter.js b/modules/centroBidAdapter.js index 9cd3ec004fa..34162f8d1ec 100644 --- a/modules/centroBidAdapter.js +++ b/modules/centroBidAdapter.js @@ -54,7 +54,7 @@ var CentroAdapter = function CentroAdapter() { query.push('sz=' + size.join('x')); } // make handler name for JSONP request - var handlerName = handlerPrefix + bid.unit + size.join('x') + encodeURIComponent(requestedBid.placementCode); + var handlerName = handlerPrefix + bid.unit + size.join('x') + encodeURIComponent(requestedBid.bidId); query.push('callback=' + encodeURIComponent('window["' + handlerName + '"]')); // maybe is needed add some random parameter to disable cache diff --git a/test/spec/modules/centroBidAdapter_spec.js b/test/spec/modules/centroBidAdapter_spec.js index 9f354e1ba56..a4bceb5de39 100644 --- a/test/spec/modules/centroBidAdapter_spec.js +++ b/test/spec/modules/centroBidAdapter_spec.js @@ -43,6 +43,7 @@ describe('centro adapter tests', function () { unit: 28136, page_url: 'http://test_url.ru' }, + bidId: '1234', placementCode: 'div-gpt-ad-12345-1' }, { @@ -51,12 +52,14 @@ describe('centro adapter tests', function () { params: { unit: 28137 }, + bidId: '5678', placementCode: 'div-gpt-ad-12345-2' }, { bidder: 'centro', sizes: [[728, 90]], params: {}, + bidId: '9101112', placementCode: 'div-gpt-ad-12345-3' } ] @@ -71,7 +74,7 @@ describe('centro adapter tests', function () { var parsedBidUrl = urlParse(bidUrl1); var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - var generatedCallback = 'window["adCentroHandler_28136300x250div-gpt-ad-12345-1"]'; + var generatedCallback = 'window["adCentroHandler_28136300x2501234"]'; expect(parsedBidUrl.hostname).to.equal('staging.brand-server.com'); expect(parsedBidUrl.pathname).to.equal('/hb'); @@ -85,7 +88,7 @@ describe('centro adapter tests', function () { parsedBidUrl = urlParse(bidUrl2); parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - generatedCallback = 'window["adCentroHandler_28137728x90div-gpt-ad-12345-2"]'; + generatedCallback = 'window["adCentroHandler_28137728x905678"]'; expect(parsedBidUrl.hostname).to.equal('t.brand-server.com'); expect(parsedBidUrl.pathname).to.equal('/hb'); @@ -117,6 +120,7 @@ describe('centro adapter tests', function () { params: { unit: 28136 }, + bidId: '12345', placementCode: '/19968336/header-bid-tag-0' }, { @@ -125,6 +129,7 @@ describe('centro adapter tests', function () { params: { unit: 111111 }, + bidId: '12346', placementCode: '/19968336/header-bid-tag-1' }, { @@ -133,6 +138,7 @@ describe('centro adapter tests', function () { params: { unit: 222222 }, + bidId: '12347', placementCode: '/19968336/header-bid-tag-2' }, { @@ -141,6 +147,7 @@ describe('centro adapter tests', function () { params: { unit: 333333 }, + bidId: '12348', placementCode: '/19968336/header-bid-tag-3' } ] @@ -149,9 +156,9 @@ describe('centro adapter tests', function () { it('callback function should exist', function () { adapter().callBids(params); - expect(window['adCentroHandler_28136300x250%2F19968336%2Fheader-bid-tag-0']) + expect(window['adCentroHandler_28136300x25012345']) .to.exist.and.to.be.a('function'); - expect(window['adCentroHandler_111111728x90%2F19968336%2Fheader-bid-tag-1']) + expect(window['adCentroHandler_111111728x9012346']) .to.exist.and.to.be.a('function'); }); @@ -180,10 +187,10 @@ describe('centro adapter tests', function () { var response3 = {'adTag': '', 'height': 0, 'value': 0, 'width': 0, 'sectionID': 222222}; var response4 = ''; - window['adCentroHandler_28136300x250%2F19968336%2Fheader-bid-tag-0'](response); - window['adCentroHandler_111111728x90%2F19968336%2Fheader-bid-tag-1'](response2); - window['adCentroHandler_222222728x90%2F19968336%2Fheader-bid-tag-2'](response3); - window['adCentroHandler_333333728x90%2F19968336%2Fheader-bid-tag-3'](response4); + window['adCentroHandler_28136300x25012345'](response); + window['adCentroHandler_111111728x9012346'](response2); + window['adCentroHandler_222222728x9012347'](response3); + window['adCentroHandler_333333728x9012348'](response4); var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; var bidObject1 = stubAddBidResponse.getCall(0).args[1]; From 265a8a9b6fbcff24fbc7caabbb2df6113efc100d Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Mon, 30 Oct 2017 06:38:20 -0600 Subject: [PATCH 33/62] Change prebidServer to call client user syncs if they exist (#1734) --- modules/prebidServerBidAdapter.js | 8 +++++ modules/rubiconBidAdapter.js | 4 +-- src/adaptermanager.js | 4 +++ src/adapters/bidderFactory.js | 33 +++++++++++-------- .../modules/prebidServerBidAdapter_spec.js | 18 ++++++++++ test/spec/modules/rubiconBidAdapter_spec.js | 9 +++-- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index 89a59fa7c67..7120d67eb56 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -183,6 +183,14 @@ function PrebidServer() { }); } + // do client-side syncs if available + requestedBidders.forEach(bidder => { + let clientAdapter = adaptermanager.getBidAdapter(bidder); + if (clientAdapter && clientAdapter.registerSyncs) { + clientAdapter.registerSyncs(); + } + }); + if (result.bids) { result.bids.forEach(bidObj => { let bidRequest = utils.getBidRequest(bidObj.bid_id); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 866e02bc258..2830711a4c9 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -287,8 +287,8 @@ export const spec = { return bids; }, []); }, - getUserSyncs: function() { - if (!hasSynced) { + getUserSyncs: function(syncOptions) { + if (!hasSynced && syncOptions.iframeEnabled) { hasSynced = true; return { type: 'iframe', diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 37ab5dffafb..34866365445 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -325,6 +325,10 @@ exports.setBidderSequence = function (order) { } }; +exports.getBidAdapter = function(bidder) { + return _bidderRegistry[bidder]; +}; + exports.setS2SConfig = function (config) { _s2sConfig = config; }; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 241891640c5..9641c4c484b 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -154,6 +154,7 @@ export function newBidder(spec) { getSpec: function() { return Object.freeze(spec); }, + registerSyncs, callBids: function(bidderRequest) { if (!Array.isArray(bidderRequest.bids)) { return; @@ -195,20 +196,7 @@ export function newBidder(spec) { const responses = []; function afterAllResponses() { fillNoBids(); - if (spec.getUserSyncs) { - let syncs = spec.getUserSyncs({ - iframeEnabled: config.getConfig('userSync.iframeEnabled'), - pixelEnabled: config.getConfig('userSync.pixelEnabled'), - }, responses); - if (syncs) { - if (!Array.isArray(syncs)) { - syncs = [syncs]; - } - syncs.forEach((sync) => { - userSync.registerSync(sync.type, spec.code, sync.url) - }); - } - } + registerSyncs(responses); } const validBidRequests = bidderRequest.bids.filter(filterAndWarn); @@ -337,6 +325,23 @@ export function newBidder(spec) { } }); + function registerSyncs(responses) { + if (spec.getUserSyncs) { + let syncs = spec.getUserSyncs({ + iframeEnabled: config.getConfig('userSync.iframeEnabled'), + pixelEnabled: config.getConfig('userSync.pixelEnabled'), + }, responses); + if (syncs) { + if (!Array.isArray(syncs)) { + syncs = [syncs]; + } + syncs.forEach((sync) => { + userSync.registerSync(sync.type, spec.code, sync.url) + }); + } + } + } + function filterAndWarn(bid) { if (!spec.isBidRequestValid(bid)) { logWarn(`Invalid bid sent to bidder ${spec.code}: ${JSON.stringify(bid)}`); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 21098a2859f..78eef4b016b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import Adapter from 'modules/prebidServerBidAdapter'; +import adapterManager from 'src/adaptermanager'; import bidmanager from 'src/bidmanager'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; @@ -353,6 +354,23 @@ describe('S2S Adapter', () => { expect(response).to.have.property('adserverTargeting').that.deep.equals({'foo': 'bar'}); }); + it('registers client user syncs when client bid adapter is present', () => { + let rubiconAdapter = { + registerSyncs: sinon.spy() + }; + sinon.stub(adapterManager, 'getBidAdapter', () => rubiconAdapter); + + server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); + + adapter.setConfig(CONFIG); + adapter.callBids(REQUEST); + server.respond(); + + sinon.assert.calledOnce(rubiconAdapter.registerSyncs); + + adapterManager.getBidAdapter.restore(); + }); + it('registers bid responses when server requests cookie sync', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6c08c66f485..f77391dffe2 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -4,6 +4,7 @@ import { spec, masSizeOrdering, resetUserSync } from 'modules/rubiconBidAdapter' import { parse as parseQuery } from 'querystring'; import { newBidder } from 'src/adapters/bidderFactory'; import { userSync } from 'src/userSync'; +import { config } from 'src/config'; var CONSTANTS = require('src/constants.json'); @@ -779,13 +780,17 @@ describe('the rubicon adapter', () => { }); it('should register the Emily iframe', () => { - let syncs = spec.getUserSyncs(); + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); }); it('should not register the Emily iframe more than once', () => { - let syncs = spec.getUserSyncs(); + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); // when called again, should still have only been called once From 8420558d2e565b1951713b9a90a940bfb9d0b248 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Mon, 30 Oct 2017 08:53:18 -0600 Subject: [PATCH 34/62] New hooks API (replaces monkey-patching for currency) (#1683) * added hook module to prebid core that allows extension of arbitrary functions * remove unused dependency tiny-queue * change PluginFunction to HookedFunction * more hook documentation fixes * allow context for hooked functions * added tests for context * remove withContext, just use bind * fix in hooks so asyncSeries keeps proper bound context --- modules/currency.js | 67 ++++++------- src/bidmanager.js | 5 +- src/hook.js | 78 +++++++++++++++ test/spec/hook_spec.js | 151 +++++++++++++++++++++++++++++ test/spec/modules/currency_spec.js | 60 +++++------- 5 files changed, 283 insertions(+), 78 deletions(-) create mode 100644 src/hook.js create mode 100644 test/spec/hook_spec.js diff --git a/modules/currency.js b/modules/currency.js index 1d0286ed569..19e7d2903f4 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -13,9 +13,6 @@ var conversionCache = {}; var currencyRatesLoaded = false; var adServerCurrency = 'USD'; -// Used as reference to the original bidmanager.addBidResponse -var originalBidResponse; - export var currencySupportEnabled = false; export var currencyRates = {}; var bidderCurrencyDefault = {}; @@ -81,12 +78,9 @@ function initCurrency(url) { conversionCache = {}; currencySupportEnabled = true; - if (!originalBidResponse) { - utils.logInfo('Installing addBidResponse decorator for currency module', arguments); + utils.logInfo('Installing addBidResponse decorator for currency module', arguments); - originalBidResponse = bidmanager.addBidResponse; - bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse); - } + bidmanager.addBidResponse.addHook(addBidResponseHook, 100); if (!currencyRates.conversions) { ajax(url, function (response) { @@ -103,12 +97,9 @@ function initCurrency(url) { } function resetCurrency() { - if (originalBidResponse) { - utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); + utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); - bidmanager.addBidResponse = originalBidResponse; - originalBidResponse = undefined; - } + bidmanager.addBidResponse.removeHook(addBidResponseHook); adServerCurrency = 'USD'; conversionCache = {}; @@ -118,37 +109,35 @@ function resetCurrency() { bidderCurrencyDefault = {}; } -export function addBidResponseDecorator(fn) { - return function(adUnitCode, bid) { - if (!bid) { - return fn.apply(this, arguments); // if no bid, call original and let it display warnings - } +export function addBidResponseHook(adUnitCode, bid, fn) { + if (!bid) { + return fn.apply(this, arguments); // if no bid, call original and let it display warnings + } - let bidder = bid.bidderCode || bid.bidder; - if (bidderCurrencyDefault[bidder]) { - let currencyDefault = bidderCurrencyDefault[bidder]; - if (bid.currency && currencyDefault !== bid.currency) { - utils.logWarn(`Currency default '${bidder}: ${currencyDefault}' ignored. adapter specified '${bid.currency}'`); - } else { - bid.currency = currencyDefault; - } + let bidder = bid.bidderCode || bid.bidder; + if (bidderCurrencyDefault[bidder]) { + let currencyDefault = bidderCurrencyDefault[bidder]; + if (bid.currency && currencyDefault !== bid.currency) { + utils.logWarn(`Currency default '${bidder}: ${currencyDefault}' ignored. adapter specified '${bid.currency}'`); + } else { + bid.currency = currencyDefault; } + } - // default to USD if currency not set - if (!bid.currency) { - utils.logWarn('Currency not specified on bid. Defaulted to "USD"'); - bid.currency = 'USD'; - } + // default to USD if currency not set + if (!bid.currency) { + utils.logWarn('Currency not specified on bid. Defaulted to "USD"'); + bid.currency = 'USD'; + } - // execute immediately if the bid is already in the desired currency - if (bid.currency === adServerCurrency) { - return fn.apply(this, arguments); - } + // execute immediately if the bid is already in the desired currency + if (bid.currency === adServerCurrency) { + return fn.apply(this, arguments); + } - bidResponseQueue.push(wrapFunction(fn, this, arguments)); - if (!currencySupportEnabled || currencyRatesLoaded) { - processBidResponseQueue(); - } + bidResponseQueue.push(wrapFunction(fn, this, arguments)); + if (!currencySupportEnabled || currencyRatesLoaded) { + processBidResponseQueue(); } } diff --git a/src/bidmanager.js b/src/bidmanager.js index c12cc4828e6..abd8fea135d 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -5,6 +5,7 @@ import { isValidVideoBid } from './video'; import { getCacheUrl, store } from './videoCache'; import { Renderer } from 'src/Renderer'; import { config } from 'src/config'; +import { createHook } from 'src/hook'; var CONSTANTS = require('./constants.json'); var AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; @@ -82,7 +83,7 @@ exports.bidsBackAll = function () { /* * This function should be called to by the bidder adapter to register a bid response */ -exports.addBidResponse = function (adUnitCode, bid) { +exports.addBidResponse = createHook('asyncSeries', function (adUnitCode, bid) { if (isValid()) { prepareBidForAuction(); @@ -250,7 +251,7 @@ exports.addBidResponse = function (adUnitCode, bid) { doCallbacksIfNeeded(); } } -}; +}); function getKeyValueTargetingPairs(bidderCode, custBidObj) { var keyValues = {}; diff --git a/src/hook.js b/src/hook.js new file mode 100644 index 00000000000..5ba1d4b9bbf --- /dev/null +++ b/src/hook.js @@ -0,0 +1,78 @@ + +/** + * @typedef {function} HookedFunction + * @property {function(function(), [number])} addHook A method that takes a new function to attach as a hook + * to the HookedFunction + * @property {function(function())} removeHook A method to remove attached hooks + */ + +/** + * A map of global hook methods to allow easy extension of hooked functions that are intended to be extended globally + * @type {{}} + */ +export const hooks = {}; + +/** + * A utility function for allowing a regular function to be extensible with additional hook functions + * @param {string} type The method for applying all attached hooks when this hooked function is called + * @param {function()} fn The function to make hookable + * @param {string} hookName If provided this allows you to register a name for a global hook to have easy access to + * the addHook and removeHook methods for that hook (which are usually accessed as methods on the function itself) + * @returns {HookedFunction} A new function that implements the HookedFunction interface + */ +export function createHook(type, fn, hookName) { + let _hooks = [{fn, priority: 0}]; + + let types = { + sync: function(...args) { + _hooks.forEach(hook => { + hook.fn.apply(this, args); + }); + }, + asyncSeries: function(...args) { + let curr = 0; + + const asyncSeriesNext = (...args) => { + let hook = _hooks[++curr]; + if (typeof hook === 'object' && typeof hook.fn === 'function') { + return hook.fn.apply(this, args.concat(asyncSeriesNext)) + } + }; + + return _hooks[curr].fn.apply(this, args.concat(asyncSeriesNext)); + } + }; + + if (!types[type]) { + throw 'invalid hook type'; + } + + let methods = { + addHook: function(fn, priority = 10) { + if (typeof fn === 'function') { + _hooks.push({ + fn, + priority: priority + }); + + _hooks.sort((a, b) => b.priority - a.priority); + } + }, + removeHook: function(removeFn) { + _hooks = _hooks.filter(hook => hook.fn === fn || hook.fn !== removeFn); + } + }; + + if (typeof hookName === 'string') { + hooks[hookName] = methods; + } + + function hookedFn(...args) { + if (_hooks.length === 0) { + return fn.apply(this, args); + } + return types[type].apply(this, args); + } + + return Object.assign(hookedFn, methods); +} diff --git a/test/spec/hook_spec.js b/test/spec/hook_spec.js new file mode 100644 index 00000000000..1fab4ecd1b7 --- /dev/null +++ b/test/spec/hook_spec.js @@ -0,0 +1,151 @@ + +import { expect } from 'chai'; +import { createHook, hooks } from 'src/hook'; + +describe('the hook module', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should call all sync hooks attached to a function', () => { + let called = []; + let calledWith; + + let testFn = () => { + called.push(testFn); + }; + let testHook = (...args) => { + called.push(testHook); + calledWith = args; + }; + let testHook2 = () => { + called.push(testHook2); + }; + let testHook3 = () => { + called.push(testHook3); + }; + + let hookedTestFn = createHook('sync', testFn, 'testHook'); + + hookedTestFn.addHook(testHook, 50); + hookedTestFn.addHook(testHook2, 100); + + // make sure global test hooks work as well (with default priority) + hooks['testHook'].addHook(testHook3); + + hookedTestFn(1, 2, 3); + + expect(called).to.deep.equal([ + testHook2, + testHook, + testHook3, + testFn + ]); + + expect(calledWith).to.deep.equal([1, 2, 3]); + + called = []; + + hookedTestFn.removeHook(testHook); + hooks['testHook'].removeHook(testHook3); + + hookedTestFn(1, 2, 3); + + expect(called).to.deep.equal([ + testHook2, + testFn + ]); + }); + + it('should allow context to be passed to hooks, but keep bound contexts', () => { + let context; + let fn = function() { + context = this; + }; + + let boundContext = {}; + let calledBoundContext; + let hook = function() { + calledBoundContext = this; + }.bind(boundContext); + + let hookFn = createHook('sync', fn); + hookFn.addHook(hook); + + let newContext = {}; + hookFn.bind(newContext)(); + + expect(context).to.equal(newContext); + expect(calledBoundContext).to.equal(boundContext); + }); + + describe('asyncSeries', () => { + it('should call function as normal if no hooks attached', () => { + let fn = sandbox.spy(); + let hookFn = createHook('asyncSeries', fn); + + hookFn(1); + + expect(fn.calledOnce).to.equal(true); + expect(fn.firstCall.args[0]).to.equal(1); + }); + + it('should call hooks correctly applied in asyncSeries', () => { + let called = []; + + let testFn = (called) => { + called.push(testFn); + }; + let testHook = (called, next) => { + called.push(testHook); + next(called); + }; + let testHook2 = (called, next) => { + called.push(testHook2); + next(called); + }; + + let hookedTestFn = createHook('asyncSeries', testFn); + hookedTestFn.addHook(testHook); + hookedTestFn.addHook(testHook2); + + hookedTestFn(called); + + expect(called).to.deep.equal([ + testHook, + testHook2, + testFn + ]); + }); + + it('should allow context to be passed to hooks, but keep bound contexts', () => { + let context; + let fn = function() { + context = this; + }; + + let boundContext1 = {}; + let calledBoundContext1; + let hook1 = function(next) { + calledBoundContext1 = this; + next() + }.bind(boundContext1); + + let hookFn = createHook('asyncSeries', fn); + hookFn.addHook(hook1); + + let newContext = {}; + hookFn = hookFn.bind(newContext); + hookFn(); + + expect(context).to.equal(newContext); + expect(calledBoundContext1).to.equal(boundContext1); + }); + }); +}); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 937e6a084e4..06faa5665c9 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -5,7 +5,7 @@ import { import { setConfig, - addBidResponseDecorator, + addBidResponseHook, currencySupportEnabled, currencyRates @@ -46,10 +46,6 @@ describe('currency', function () { var bid = { cpm: 1, bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - innerBid = bid; - }); - setConfig({ adServerCurrency: 'GBP', bidderCurrencyDefault: { @@ -57,7 +53,9 @@ describe('currency', function () { } }); - wrappedAddBidResponseFn('elementId', bid); + addBidResponseHook('elementId', bid, function(adCodeId, bid) { + innerBid = bid; + }); expect(innerBid.currency).to.equal('GBP') }); @@ -68,10 +66,6 @@ describe('currency', function () { var bid = { cpm: 1, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - innerBid = bid; - }); - setConfig({ adServerCurrency: 'JPY', bidderCurrencyDefault: { @@ -79,7 +73,9 @@ describe('currency', function () { } }); - wrappedAddBidResponseFn('elementId', bid); + addBidResponseHook('elementId', bid, function(adCodeId, bid) { + innerBid = bid; + }); expect(innerBid.currency).to.equal('JPY') }); @@ -97,12 +93,10 @@ describe('currency', function () { var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); - expect(innerBid.cpm).to.equal('1.0000'); }); }); @@ -113,14 +107,15 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); - var marker = false; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - marker = true; - }); var bid = { 'cpm': 1, 'currency': 'USD' }; setConfig({ 'adServerCurrency': 'JPY' }); - wrappedAddBidResponseFn('elementId', bid); + + var marker = false; + addBidResponseHook('elementId', bid, function() { + marker = true; + }); + expect(marker).to.equal(false); fakeCurrencyFileServer.respond(); @@ -133,10 +128,9 @@ describe('currency', function () { setConfig({}); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal(1); }); @@ -144,10 +138,9 @@ describe('currency', function () { setConfig({}); var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -157,10 +150,9 @@ describe('currency', function () { }); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(bid).to.equal(innerBid); }); @@ -170,10 +162,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'ABC' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -183,10 +174,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -196,10 +186,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal(1); expect(innerBid.currency).to.equal('JPY'); }); @@ -210,10 +199,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.7798'); expect(innerBid.currency).to.equal('GBP'); }); @@ -224,10 +212,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'CNY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.1133'); expect(innerBid.currency).to.equal('GBP'); }); @@ -238,10 +225,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.0623'); expect(innerBid.currency).to.equal('CNY'); }); From 47019481bb34f4071f85d899a16727d8aae606e0 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 30 Oct 2017 10:55:19 -0400 Subject: [PATCH 35/62] Add `usePaymentRule` param to AN bidders (#1778) --- modules/appnexusAstBidAdapter.js | 1 + modules/appnexusBidAdapter.js | 4 ++++ test/spec/modules/appnexusAstBidAdapter_spec.js | 17 +++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/modules/appnexusAstBidAdapter.js b/modules/appnexusAstBidAdapter.js index 3c900e15312..f3173dc15bc 100644 --- a/modules/appnexusAstBidAdapter.js +++ b/modules/appnexusAstBidAdapter.js @@ -248,6 +248,7 @@ function bidToTag(bid) { tag.code = bid.params.invCode; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false tag.prebid = true; tag.disable_psa = true; if (bid.params.reserve) { diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 3d6549542dc..1fb48b68fc4 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -44,12 +44,15 @@ AppNexusAdapter = function AppNexusAdapter() { var query = utils.getBidIdParameter('query', bid.params); var referrer = utils.getBidIdParameter('referrer', bid.params); var altReferrer = utils.getBidIdParameter('alt_referrer', bid.params); + let usePaymentRule = utils.getBidIdParameter('usePaymentRule', bid.params); var jptCall = '//ib.adnxs.com/jpt?'; jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleAnCB'); jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + jptCall = utils.tryAppendQueryString(jptCall, 'use_pmt_rule', usePaymentRule); + if (member) { jptCall = utils.tryAppendQueryString(jptCall, 'member', member); } else if (memberId) { @@ -106,6 +109,7 @@ AppNexusAdapter = function AppNexusAdapter() { delete paramsCopy.referrer; delete paramsCopy.alt_referrer; delete paramsCopy.member; + delete paramsCopy.usePaymentRule; // get the reminder var queryParams = utils.parseQueryStringParameters(paramsCopy); diff --git a/test/spec/modules/appnexusAstBidAdapter_spec.js b/test/spec/modules/appnexusAstBidAdapter_spec.js index 83cbcc38a2b..3884b1c5863 100644 --- a/test/spec/modules/appnexusAstBidAdapter_spec.js +++ b/test/spec/modules/appnexusAstBidAdapter_spec.js @@ -244,6 +244,23 @@ describe('AppNexusAdapter', () => { 'value': ['123'] }]); }); + + it('should should add payment rules to the request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); }) describe('interpretResponse', () => { From ac6775b72463004e34292bd82108541cfb28ac6a Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 30 Oct 2017 21:56:38 +0300 Subject: [PATCH 36/62] Update GetIntent adapter to 1.0 version (#1721) * AD-2311: Make GetIntent adapter compatible with Prebid.js 1.0 version * AD-2311: remove blank line * Trigger * GetIntent adapter - added bid response fields: currency, ttl and netRevenue; fixed creative size selector (#1721) * GetIntent adapter - added bid response fields: bidId, creativeId (#1721) --- modules/getintentBidAdapter.js | 191 +++++++++----- modules/getintentBidAdapter.md | 48 ++++ test/spec/modules/getintentBidAdapter_spec.js | 249 ++++++++---------- 3 files changed, 293 insertions(+), 195 deletions(-) create mode 100644 modules/getintentBidAdapter.md diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 72f1c1a0073..f677b107529 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,78 +1,143 @@ -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; +import { registerBidder } from 'src/adapters/bidderFactory'; -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); +const BIDDER_CODE = 'getintent'; +const IS_NET_REVENUE = true; +const BID_HOST = 'px.adhigh.net'; +const BID_BANNER_PATH = '/rtb/direct_banner'; +const BID_VIDEO_PATH = '/rtb/direct_vast'; +const BID_RESPONSE_TTL_SEC = 360; +const VIDEO_PROPERTIES = [ + 'protocols', 'mimes', 'min_dur', 'max_dur', 'min_btr', 'max_btr', 'vi_format', 'api', 'skippable' +]; +const OPTIONAL_PROPERTIES = [ + 'cur', 'floor' +]; -var GetIntentAdapter = function GetIntentAdapter() { - var headerBiddingStaticJS = window.location.protocol + '//cdn.adhigh.net/adserver/hb.js'; +export const spec = { + code: BIDDER_CODE, + aliases: ['getintentAdapter'], + supportedMediaTypes: ['video', 'banner'], - function _callBids(params) { - if (typeof window.gi_hb === 'undefined') { - adloader.loadScript(headerBiddingStaticJS, function() { - bid(params); - }, true); - } else { - bid(params); + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + * */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.pid && bid.params.tid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests - an array of bids. + * @return ServerRequest[] + */ + buildRequests: function(bidRequests) { + return bidRequests.map(bidRequest => { + let giBidRequest = buildGiBidRequest(bidRequest); + return { + method: 'GET', + url: buildUrl(giBidRequest), + data: giBidRequest, + }; + }); + }, + + /** + * Callback for bids, after the call to DSP completes. + * Parse the response from the server into a list of bids. + * + * @param {object} serverResponse A response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse) { + let responseBody = serverResponse.body; + const bids = []; + if (responseBody && responseBody.no_bid !== 1) { + let size = parseSize(responseBody.size); + let bid = { + requestId: responseBody.bid_id, + ttl: BID_RESPONSE_TTL_SEC, + netRevenue: IS_NET_REVENUE, + currency: responseBody.currency, + creativeId: responseBody.creative_id, + cpm: responseBody.cpm, + width: size[0], + height: size[1] + }; + if (responseBody.vast_url) { + bid.mediaType = 'video'; + bid.vastUrl = responseBody.vast_url; + } else { + bid.mediaType = 'banner'; + bid.ad = responseBody.ad; + } + bids.push(bid); } + return bids; } - function addOptional(params, request, props) { - for (var i = 0; i < props.length; i++) { - if (params.hasOwnProperty(props[i])) { - request[props[i]] = params[props[i]]; +} + +function buildUrl(bid) { + return '//' + BID_HOST + (bid.is_video ? BID_VIDEO_PATH : BID_BANNER_PATH); +} + +/** + * Builds GI bid request from BidRequest. + * + * @param {BidRequest} bidRequest. + * @return {object} GI bid request. + * */ +function buildGiBidRequest(bidRequest) { + let giBidRequest = { + bid_id: bidRequest.bidId, + pid: bidRequest.params.pid, // required + tid: bidRequest.params.tid, // required + known: bidRequest.params.known || 1, + is_video: bidRequest.mediaType === 'video', + resp_type: 'JSON' + }; + if (bidRequest.sizes) { + giBidRequest.size = produceSize(bidRequest.sizes); + } + addVideo(bidRequest.params.video, giBidRequest); + addOptional(bidRequest.params, giBidRequest, OPTIONAL_PROPERTIES); + return giBidRequest; +} + +function addVideo(video, giBidRequest) { + if (giBidRequest.is_video && video) { + for (let i = 0, l = VIDEO_PROPERTIES.length; i < l; i++) { + let key = VIDEO_PROPERTIES[i]; + if (video.hasOwnProperty(key)) { + giBidRequest[key] = Array.isArray(video[key]) ? video[key].join(',') : video[key]; } } } +} - function bid(params) { - var bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - var bidRequest = bids[i]; - var request = { - pid: bidRequest.params.pid, // required - tid: bidRequest.params.tid, // required - known: bidRequest.params.known || 1, - is_video: bidRequest.mediaType === 'video', - video: bidRequest.params.video || {}, - size: bidRequest.sizes[0].join('x'), - }; - addOptional(bidRequest.params, request, ['cur', 'floor']); - (function (r, br) { - window.gi_hb.makeBid(r, function(bidResponse) { - if (bidResponse.no_bid === 1) { - var nobid = bidfactory.createBid(STATUS.NO_BID); - nobid.bidderCode = br.bidder; - bidmanager.addBidResponse(br.placementCode, nobid); - } else { - var bid = bidfactory.createBid(STATUS.GOOD); - var size = bidResponse.size.split('x'); - bid.bidderCode = br.bidder; - bid.cpm = bidResponse.cpm; - bid.width = size[0]; - bid.height = size[1]; - if (br.mediaType === 'video') { - bid.vastUrl = bidResponse.vast_url; - bid.descriptionUrl = bidResponse.vast_url; - bid.mediaType = 'video'; - } else { - bid.ad = bidResponse.ad; - } - bidmanager.addBidResponse(br.placementCode, bid); - } - }); - })(request, bidRequest); +function addOptional(params, request, props) { + for (let i = 0; i < props.length; i++) { + if (params.hasOwnProperty(props[i])) { + request[props[i]] = params[props[i]]; } } +} - return { - callBids: _callBids - }; -}; +function parseSize(s) { + return s.split('x').map(Number); +} -adaptermanager.registerBidAdapter(new GetIntentAdapter(), 'getintent', { - supportedMediaTypes: ['video'] -}); +function produceSize(sizes) { + // TODO: add support for multiple sizes + if (Array.isArray(sizes[0])) { + return sizes[0].join('x'); + } else { + return sizes.join('x'); + } +} -module.exports = GetIntentAdapter; +registerBidder(spec); diff --git a/modules/getintentBidAdapter.md b/modules/getintentBidAdapter.md new file mode 100644 index 00000000000..7f9b38f6b22 --- /dev/null +++ b/modules/getintentBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +``` +Module Name: GetIntent Bidder Adapter +Module Type: Bidder Adapter +Maintainer: server-dev@getintent.com +``` + +# Description + +Module that connects to GetIntent's demand sources. +Banner and Video formats are supported. + +# Required parameters +* ```pid``` for Publisher ID +* ```tid``` for Tag ID. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: "getintent", + params: { + pid: "7", + tid: "test01" + } + } + ] + },{ + code: 'test-video-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: "getintent", + params: { + pid: "7", + tid: "test01" + }, + mediaType: "video" + } + ] + } + ]; +``` diff --git a/test/spec/modules/getintentBidAdapter_spec.js b/test/spec/modules/getintentBidAdapter_spec.js index e66d2138eaf..1b76c4852b4 100644 --- a/test/spec/modules/getintentBidAdapter_spec.js +++ b/test/spec/modules/getintentBidAdapter_spec.js @@ -1,146 +1,131 @@ -import Adapter from '../../../modules/getintentBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import {expect} from 'chai'; - -var assert = require('chai').assert; - -describe('getintent media adapter test', () => { - let adapter; - - window.gi_hb = { - makeBid: function(bidRequest, callback) { - var pid = bidRequest.pid; - var tid = bidRequest.tid; - - if (pid == 'p1' || pid == 'p2') { - callback({ - ad: `Ad Markup ${pid} ${tid}`, - cpm: 2.71, - size: `${bidRequest.size}` - }, bidRequest); - } else if (pid == 'p3') { - callback({ - no_bid: 1 - }, bidRequest); - } else if (pid == 'p4') { - callback({ - vast_url: `http://test.com?pid=${pid}&tid=${tid}`, - cpm: 2.88, - size: `${bidRequest.size}` - }, bidRequest); +import { expect } from 'chai' +import { spec } from 'modules/getintentBidAdapter' + +describe('GetIntent Adapter Tests:', () => { + const bidRequests = [{ + bidId: 'bid12345', + params: { + pid: 'p1000', + tid: 't1000' + }, + sizes: [[300, 250]] + }]; + const videoBidRequest = { + bidId: 'bid789', + params: { + pid: 'p1001', + tid: 't1001', + video: { + mimes: ['video/mp4', 'application/javascript'], + max_dur: 20, + api: [1, 2], + skippable: true } - } + }, + sizes: [300, 250], + mediaType: 'video' }; - function callOut() { - adapter.callBids({ - bidderCode: 'getintent', - bids: [ - { - bidder: 'getintent', - adUnitCode: 'test1', - sizes: [[320, 240]], - params: { - pid: 'p1', - tid: 't1', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test2', - sizes: [[720, 90]], - params: { - pid: 'p2', - tid: 't1', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test3', - sizes: [[400, 500]], - params: { - pid: 'p3', - tid: 't2', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test4', - mediaType: 'video', - sizes: [[480, 352]], - params: { - pid: 'p4', - tid: 't3', - cur: 'USD' - } - } - ] - }); - } - - beforeEach(() => { - adapter = new Adapter(); + it('Verify build request', () => { + const serverRequests = spec.buildRequests(bidRequests); + let serverRequest = serverRequests[0]; + expect(serverRequest.url).to.equal('//px.adhigh.net/rtb/direct_banner'); + expect(serverRequest.method).to.equal('GET'); + expect(serverRequest.data.bid_id).to.equal('bid12345'); + expect(serverRequest.data.pid).to.equal('p1000'); + expect(serverRequest.data.tid).to.equal('t1000'); + expect(serverRequest.data.size).to.equal('300x250'); + expect(serverRequest.data.is_video).to.equal(false); }); - afterEach(() => { + it('Verify build video request', () => { + const serverRequests = spec.buildRequests([videoBidRequest]); + let serverRequest = serverRequests[0]; + expect(serverRequest.url).to.equal('//px.adhigh.net/rtb/direct_vast'); + expect(serverRequest.method).to.equal('GET'); + expect(serverRequest.data.bid_id).to.equal('bid789'); + expect(serverRequest.data.pid).to.equal('p1001'); + expect(serverRequest.data.tid).to.equal('t1001'); + expect(serverRequest.data.size).to.equal('300x250'); + expect(serverRequest.data.is_video).to.equal(true); + expect(serverRequest.data.mimes).to.equal('video/mp4,application/javascript'); + expect(serverRequest.data.max_dur).to.equal(20); + expect(serverRequest.data.api).to.equal('1,2'); + expect(serverRequest.data.skippable).to.equal(true); }); - describe('adding bids to the manager', () => { - let firstBid; - let secondBid; - let thirdBid; - let videoBid; - - beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - callOut(); - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - thirdBid = bidManager.addBidResponse.thirdCall.args[1]; - videoBid = bidManager.addBidResponse.lastCall.args[1]; - }); - - afterEach(() => { - bidManager.addBidResponse.restore(); - }); - - it('was called four times', () => { - assert.strictEqual(bidManager.addBidResponse.callCount, 4); - }); + it('Verify parse response', () => { + const serverResponse = { + body: { + bid_id: 'bid12345', + cpm: 2.25, + currency: 'USD', + size: '300x250', + creative_id: '1000', + ad: 'Ad markup' + }, + headers: { + } + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(2.25); + expect(bid.currency).to.equal('USD'); + expect(bid.creativeId).to.equal('1000'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.requestId).to.equal('bid12345'); + expect(bid.mediaType).to.equal('banner'); + expect(bid.ad).to.equal('Ad markup'); + }); - it('will respond to the first bid', () => { - expect(firstBid).to.have.property('ad', 'Ad Markup p1 t1'); - expect(firstBid).to.have.property('cpm', 2.71); - expect(firstBid).to.have.property('width', '320'); - expect(firstBid).to.have.property('height', '240'); - }); + it('Verify parse video response', () => { + const serverResponse = { + body: { + bid_id: 'bid789', + cpm: 3.25, + currency: 'USD', + size: '300x250', + creative_id: '2000', + vast_url: '//vast.xml/url' + }, + headers: { + } + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3.25); + expect(bid.currency).to.equal('USD'); + expect(bid.creativeId).to.equal('2000'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.requestId).to.equal('bid789'); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('//vast.xml/url'); + }); - it('will respond to the second bid', () => { - expect(secondBid).to.have.property('ad', 'Ad Markup p2 t1'); - expect(secondBid).to.have.property('cpm', 2.71); - expect(secondBid).to.have.property('width', '720'); - expect(secondBid).to.have.property('height', '90'); - }); + it('Verify bidder code', () => { + expect(spec.code).to.equal('getintent'); + }); - it('wont respond to the third bid', () => { - expect(thirdBid).to.not.have.property('ad'); - expect(thirdBid).to.not.have.property('cpm'); - }); + it('Verify bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('getintentAdapter'); + }); - it('will add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'getintent'); - expect(secondBid).to.have.property('bidderCode', 'getintent'); - expect(thirdBid).to.have.property('bidderCode', 'getintent'); - }); + it('Verify supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('video'); + expect(spec.supportedMediaTypes[1]).to.equal('banner'); + }); - it('will respond to the video bid', () => { - expect(videoBid).to.have.property('vastUrl', 'http://test.com?pid=p4&tid=t3'); - expect(videoBid).to.have.property('cpm', 2.88); - expect(videoBid).to.have.property('width', '480'); - expect(videoBid).to.have.property('height', '352'); - }); + it('Verify if bid request valid', () => { + expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { test: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { pid: 111, tid: 222 } })).to.equal(true); }); }); From 377f87e27c425abd1688da8cc0e11c14aee70a44 Mon Sep 17 00:00:00 2001 From: jbartek-improve <31618107+jbartek-improve@users.noreply.github.com> Date: Tue, 31 Oct 2017 18:04:04 +0100 Subject: [PATCH 37/62] Add TTL parameter to bid (#1784) * Update Improve Digital adapter for Prebid 1.0 * Removed bidderCode from bids * Added creativeId to bid response; updated format of the first argument of interpretResponse * Added bid ttl --- modules/improvedigitalBidAdapter.js | 1 + test/spec/modules/improvedigitalBidAdapter_spec.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index bc00127b269..82c045b9db6 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -83,6 +83,7 @@ export const spec = { bid.height = bidObject.h; bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; bid.requestId = bidObject.id; + bid.ttl = 300; bid.width = bidObject.w; bids.push(bid); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 750eecc2a7d..3f93a62e850 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -204,6 +204,7 @@ describe('Improve Digital Adapter Tests', function () { 'height': 290, 'netRevenue': false, 'requestId': '33e9500b21129f', + 'ttl': 300, 'width': 600 } ]; @@ -219,6 +220,7 @@ describe('Improve Digital Adapter Tests', function () { 'height': 400, 'netRevenue': true, 'requestId': '1234', + 'ttl': 300, 'width': 700 } ]; From ec9454afd5fbe6f3b8d88e692293d937c90a16a1 Mon Sep 17 00:00:00 2001 From: Rich Loveland Date: Tue, 31 Oct 2017 13:07:04 -0400 Subject: [PATCH 38/62] Remove 'supported' from analytics adapter info (#1780) (Prebid.org doesn't endorse or support any particular adapter.) --- src/prebid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 54dc9c55118..3d25eff6761 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -635,7 +635,7 @@ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { * For usage, see [Integrate with the Prebid Analytics * API](http://prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). * - * For a list of supported analytics adapters, see [Analytics for + * For a list of analytics adapters, see [Analytics for * Prebid](http://prebid.org/overview/analytics.html). * @param {Object} config * @param {string} config.provider The name of the provider, e.g., `"ga"` for Google Analytics. From a62f917252ac0a873e2335cd165995fa7390c06a Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Tue, 31 Oct 2017 14:18:27 -0400 Subject: [PATCH 39/62] Added adUnitCode for compatibility (#1781) --- src/adapters/bidderFactory.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 9641c4c484b..0b4b0d7cd0c 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -207,6 +207,10 @@ export function newBidder(spec) { const bidRequestMap = {}; validBidRequests.forEach(bid => { bidRequestMap[bid.bidId] = bid; + // Delete this once we are 1.0 + if (!bid.adUnitCode) { + bid.adUnitCode = bid.placementCode + } }); let requests = spec.buildRequests(validBidRequests, bidderRequest); From 99f7ca77c99801387ab322d34c5ab84d4fb2a108 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Tue, 31 Oct 2017 12:31:06 -0600 Subject: [PATCH 40/62] Update rubicon adapter with new properties and 1.0 changes (#1776) * don't set bidderCode in adapter anymore for rubicon * update rubiconBidAdapter with new properties --- modules/rubiconBidAdapter.js | 27 +++++++++--------- test/spec/modules/rubiconBidAdapter_spec.js | 31 +++++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 2830711a4c9..2b7b0061430 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,10 +1,8 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; -// use deferred function call since version isn't defined yet at this point -function getIntegration() { - return 'pbjs_lite_' + $$PREBID_GLOBAL$$.version; -} +const INTEGRATION = 'pbjs_lite_v$prebid.version$'; function isSecure() { return location.protocol === 'https:'; @@ -113,7 +111,7 @@ export const spec = { page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, resolution: _getScreenResolution(), account_id: params.accountId, - integration: getIntegration(), + integration: INTEGRATION, timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), stash_creatives: true, ae_pass_through_parameters: params.video.aeParams, @@ -126,8 +124,8 @@ export const spec = { zone_id: params.zoneId, position: params.position || 'btf', floor: parseFloat(params.floor) > 0.01 ? params.floor : 0.01, - element_id: bidRequest.placementCode, - name: bidRequest.placementCode, + element_id: bidRequest.adUnitCode, + name: bidRequest.adUnitCode, language: params.video.language, width: size[0], height: size[1], @@ -187,7 +185,7 @@ export const spec = { 'p_pos', position, 'rp_floor', floor, 'rp_secure', isSecure() ? '1' : '0', - 'tk_flint', getIntegration(), + 'tk_flint', INTEGRATION, 'tid', bidRequest.transactionId, 'p_screen_res', _getScreenResolution(), 'kw', keywords, @@ -240,7 +238,7 @@ export const spec = { // video ads array is wrapped in an object if (typeof bidRequest === 'object' && bidRequest.mediaType === 'video' && typeof ads === 'object') { - ads = ads[bidRequest.placementCode]; + ads = ads[bidRequest.adUnitCode]; } // check the ad response @@ -259,10 +257,11 @@ export const spec = { let bid = { requestId: bidRequest.bidId, currency: 'USD', - creative_id: ad.creative_id, - bidderCode: spec.code, + creativeId: ad.creative_id, cpm: ad.cpm || 0, - dealId: ad.deal + dealId: ad.deal, + ttl: 300, // 5 minutes + netRevenue: config.getConfig('rubicon.netRevenue') || false }; if (bidRequest.mediaType === 'video') { bid.width = bidRequest.params.video.playerWidth; @@ -280,7 +279,7 @@ export const spec = { .reduce((memo, item) => { memo[item.key] = item.values[0]; return memo; - }, {'rpfl_elemid': bidRequest.placementCode}); + }, {'rpfl_elemid': bidRequest.adUnitCode}); bids.push(bid); @@ -308,7 +307,7 @@ function _getScreenResolution() { function _getDigiTrustQueryParams() { function getDigiTrustId() { - let digiTrustUser = window.DigiTrust && ($$PREBID_GLOBAL$$.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; } let digiTrustId = getDigiTrustId(); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index f77391dffe2..552a86b5ac4 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -79,7 +79,7 @@ describe('the rubicon adapter', () => { position: 'atf', referrer: 'localhost' }, - placementCode: '/19968336/header-bid-tag-0', + adUnitCode: '/19968336/header-bid-tag-0', sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', @@ -313,16 +313,14 @@ describe('the rubicon adapter', () => { window.DigiTrust = { getUser: sandbox.spy() }; - origGetConfig = window.$$PREBID_GLOBAL$$.getConfig; }); afterEach(() => { delete window.DigiTrust; - window.$$PREBID_GLOBAL$$.getConfig = origGetConfig; }); it('should send digiTrustId config params', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig', (key) => { var config = { digiTrustId: { success: true, @@ -355,7 +353,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to optout', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig', (key) => { var config = { digiTrustId: { success: true, @@ -384,7 +382,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to failure', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig', (key) => { var config = { digiTrustId: { success: false, @@ -413,7 +411,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params if they do not exist', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig', (key) => { var config = {}; return config[key]; }); @@ -474,8 +472,8 @@ describe('the rubicon adapter', () => { expect(slot.zone_id).to.equal('335918'); expect(slot.position).to.equal('atf'); expect(slot.floor).to.equal(0.01); - expect(slot.element_id).to.equal(bidderRequest.bids[0].placementCode); - expect(slot.name).to.equal(bidderRequest.bids[0].placementCode); + expect(slot.element_id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(slot.name).to.equal(bidderRequest.bids[0].adUnitCode); expect(slot.language).to.equal('en'); expect(slot.width).to.equal(640); expect(slot.height).to.equal(320); @@ -611,11 +609,12 @@ describe('the rubicon adapter', () => { expect(bids).to.be.lengthOf(2); - expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].width).to.equal(320); expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); - expect(bids[0].creative_id).to.equal('crid-9'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(false); + expect(bids[0].creativeId).to.equal('crid-9'); expect(bids[0].currency).to.equal('USD'); expect(bids[0].ad).to.contain(`alert('foo')`) .and.to.contain(``) @@ -623,11 +622,12 @@ describe('the rubicon adapter', () => { expect(bids[0].rubiconTargeting.rpfl_elemid).to.equal('/19968336/header-bid-tag-0'); expect(bids[0].rubiconTargeting.rpfl_14062).to.equal('43_tier_all_test'); - expect(bids[1].bidderCode).to.equal('rubicon'); expect(bids[1].width).to.equal(300); expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); - expect(bids[1].creative_id).to.equal('crid-9'); + expect(bids[1].ttl).to.equal(300); + expect(bids[1].netRevenue).to.equal(false); + expect(bids[1].creativeId).to.equal('crid-9'); expect(bids[1].currency).to.equal('USD'); expect(bids[1].ad).to.contain(`alert('foo')`) .and.to.contain(``) @@ -759,9 +759,10 @@ describe('the rubicon adapter', () => { expect(bids).to.be.lengthOf(1); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].creative_id).to.equal('crid-999999'); + expect(bids[0].creativeId).to.equal('crid-999999'); expect(bids[0].cpm).to.equal(1); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(false); expect(bids[0].descriptionUrl).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); expect(bids[0].vastUrl).to.equal( 'https://fastlane-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' From 5802bb917c054d2b3fad325f1435625852adbd3a Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Tue, 31 Oct 2017 11:48:37 -0700 Subject: [PATCH 41/62] Update dfp.buildVideoUrl to accept adserver url (#1663) * Update dfp.buildVideoUrl to accept adserver url * Reject invalid param usage * Don't overwrite description_url if cache is disabled and input contains description_url * Reject xml-only bids when cache is disabled * Accept both url and params object in function call * Fix conflict and nobid condition * Update docs and refactor based on code review --- modules/dfpAdServerVideo.js | 71 ++++++++++++++++++++-- src/utils.js | 2 +- src/video.js | 12 +++- test/spec/modules/dfpAdServerVideo_spec.js | 58 ++++++++++++++++++ test/spec/video_spec.js | 17 +++++- 5 files changed, 153 insertions(+), 7 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 4f56355a70c..591b8f7baf3 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -4,8 +4,9 @@ import { registerVideoSupport } from '../src/adServerManager'; import { getWinningBids } from '../src/targeting'; -import { formatQS, format as buildUrl } from '../src/url'; -import { parseSizesInput } from '../src/utils'; +import { formatQS, format as buildUrl, parse } from '../src/url'; +import { deepAccess, isEmpty, logError, parseSizesInput } from '../src/utils'; +import { config } from '../src/config'; /** * @typedef {Object} DfpVideoParams @@ -31,8 +32,9 @@ import { parseSizesInput } from '../src/utils'; * @param [Object] bid The bid which should be considered alongside the rest of the adserver's demand. * If this isn't defined, then we'll use the winning bid for the adUnit. * - * @param {DfpVideoParams} params Query params which should be set on the DFP request. + * @param {DfpVideoParams} [params] Query params which should be set on the DFP request. * These will override this module's defaults whenever they conflict. + * @param {string} [url] video adserver url */ /** Safe defaults which work on pretty much all video calls. */ @@ -55,9 +57,26 @@ const defaultParamConstants = { * demand in DFP. */ export default function buildDfpVideoUrl(options) { + if (!options.params && !options.url) { + logError(`A params object or a url is required to use pbjs.adServers.dfp.buildVideoUrl`); + return; + } + const adUnit = options.adUnit; const bid = options.bid || getWinningBids(adUnit.code)[0]; + let urlComponents = {}; + + if (options.url) { + // when both `url` and `params` are given, parsed url will be overwriten + // with any matching param components + urlComponents = parse(options.url); + + if (isEmpty(options.params)) { + return buildUrlFromAdserverUrlComponents(urlComponents, bid); + } + } + const derivedParams = { correlator: Date.now(), sz: parseSizesInput(adUnit.sizes).join('|'), @@ -73,9 +92,14 @@ export default function buildDfpVideoUrl(options) { const queryParams = Object.assign({}, defaultParamConstants, + urlComponents.search, derivedParams, options.params, - { cust_params: encodeURIComponent(formatQS(customParams)) }); + { cust_params: encodeURIComponent(formatQS(customParams)) } + ); + + const descriptionUrl = getDescriptionUrl(bid, options, 'params'); + if (descriptionUrl) { queryParams.description_url = descriptionUrl; } return buildUrl({ protocol: 'https', @@ -85,6 +109,45 @@ export default function buildDfpVideoUrl(options) { }); } +/** + * Builds a video url from a base dfp video url and a winning bid, appending + * Prebid-specific key-values. + * @param {Object} components base video adserver url parsed into components object + * @param {AdapterBidResponse} bid winning bid object to append parameters from + * @return {string} video url + */ +function buildUrlFromAdserverUrlComponents(components, bid) { + const descriptionUrl = getDescriptionUrl(bid, components, 'search'); + if (descriptionUrl) { components.search.description_url = descriptionUrl; } + + const adserverTargeting = (bid && bid.adserverTargeting) || {}; + const customParams = Object.assign({}, + adserverTargeting, + ); + components.search.cust_params = encodeURIComponent(formatQS(customParams)); + + return buildUrl(components); +} + +/** + * Returns the encoded vast url if it exists on a bid object, only if prebid-cache + * is disabled, and description_url is not already set on a given input + * @param {AdapterBidResponse} bid object to check for vast url + * @param {Object} components the object to check that description_url is NOT set on + * @param {string} prop the property of components that would contain description_url + * @return {string | undefined} The encoded vast url if it exists, or undefined + */ +function getDescriptionUrl(bid, components, prop) { + if (config.getConfig('usePrebidCache')) { return; } + + if (!deepAccess(components, `${prop}.description_url`)) { + const vastUrl = bid && bid.vastUrl; + if (vastUrl) { return encodeURIComponent(vastUrl); } + } else { + logError(`input cannnot contain description_url`); + } +} + registerVideoSupport('dfp', { buildVideoUrl: buildDfpVideoUrl }); diff --git a/src/utils.js b/src/utils.js index 00a06fcb091..9efa4f53c57 100644 --- a/src/utils.js +++ b/src/utils.js @@ -339,7 +339,7 @@ exports.isNumber = function(object) { */ exports.isEmpty = function (object) { if (!object) return true; - if (this.isArray(object) || this.isStr(object)) { + if (exports.isArray(object) || exports.isStr(object)) { return !(object.length > 0); } diff --git a/src/video.js b/src/video.js index 386b6b692e9..f5203e4b198 100644 --- a/src/video.js +++ b/src/video.js @@ -1,5 +1,6 @@ import { videoAdapters } from './adaptermanager'; -import { getBidRequest, deepAccess } from './utils'; +import { getBidRequest, deepAccess, logError } from './utils'; +import { config } from '../src/config'; const VIDEO_MEDIA_TYPE = 'video'; const OUTSTREAM = 'outstream'; @@ -32,6 +33,15 @@ export function isValidVideoBid(bid) { // if context not defined assume default 'instream' for video bids // instream bids require a vast url or vast xml content if (!bidRequest || (videoMediaType && context !== OUTSTREAM)) { + // xml-only video bids require prebid-cache to be enabled + if (!config.getConfig('usePrebidCache') && bid.vastXml && !bid.vastUrl) { + logError(` + This bid contains only vastXml and will not work when prebid-cache is disabled. + Try enabling prebid-cache with pbjs.setConfig({ usePrebidCache: true }); + `); + return false; + } + return !!(bid.vastUrl || bid.vastXml); } diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 3156c628abd..07439be126c 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -4,6 +4,7 @@ import parse from 'url-parse'; import buildDfpVideoUrl from 'modules/dfpAdServerVideo'; import { parseQS } from 'src/url'; import adUnit from 'test/fixtures/video/adUnit'; +import { newConfig } from 'src/config'; const bid = { videoCacheKey: 'abc', @@ -36,6 +37,43 @@ describe('The DFP video support module', () => { expect(queryParams).to.have.property('url'); }); + it('can take an adserver url as a parameter', () => { + const bidCopy = Object.assign({ }, bid); + bidCopy.vastUrl = 'vastUrl.example'; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + url: 'https://video.adserver.example/', + })); + + expect(url.host).to.equal('video.adserver.example'); + + const queryObject = parseQS(url.query); + expect(queryObject.description_url).to.equal('vastUrl.example'); + }); + + it('requires a params object or url', () => { + const url = buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + }); + + expect(url).to.be.undefined; + }); + + it('overwrites url params when both url and params object are given', () => { + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s', + params: { iu: 'my/adUnit' } + })); + + const queryObject = parseQS(url.query); + expect(queryObject.iu).to.equal('my/adUnit'); + }); + it('should override param defaults with user-provided ones', () => { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -92,6 +130,26 @@ describe('The DFP video support module', () => { expect(customParams).to.have.property('my_targeting', 'foo'); }); + it('should not overwrite an existing description_url for object input and cache disabled', () => { + const config = newConfig(); + config.setConfig({ usePrebidCache: true }); + + const bidCopy = Object.assign({}, bid); + bidCopy.vastUrl = 'vastUrl.example'; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + iu: 'my/adUnit', + description_url: 'descriptionurl.example' + } + })); + + const queryObject = parseQS(url.query); + expect(queryObject.description_url).to.equal('descriptionurl.example'); + }); + it('should work with nobid responses', () => { const url = buildDfpVideoUrl({ adUnit: adUnit, diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 57a7f7a127e..512b56c334f 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,5 +1,6 @@ import { isValidVideoBid } from 'src/video'; -const utils = require('src/utils'); +import { newConfig } from 'src/config'; +import * as utils from 'src/utils'; describe('video.js', () => { afterEach(() => { @@ -34,6 +35,20 @@ describe('video.js', () => { expect(valid).to.be(false); }); + it('catches invalid bids when prebid-cache is disabled', () => { + sinon.stub(utils, 'getBidRequest', () => ({ + bidder: 'vastOnlyVideoBidder', + mediaTypes: { video: {} }, + })); + + const config = newConfig(); + config.setConfig({ usePrebidCache: false }); + + const valid = isValidVideoBid({ vastXml: 'vast' }); + + expect(valid).to.be(false); + }); + it('validates valid outstream bids', () => { sinon.stub(utils, 'getBidRequest', () => ({ bidder: 'appnexusAst', From 7e541813ddf604fdd2315a9af64f14e2f9d22559 Mon Sep 17 00:00:00 2001 From: harpere Date: Tue, 31 Oct 2017 16:30:29 -0400 Subject: [PATCH 42/62] Commenting out tests that are failing in IE10 (#1710) * temporarily commenting out tests that are failing in IE10 * Update utils_spec.js --- test/spec/utils_spec.js | 171 +++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 83 deletions(-) diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 4ffc6a9f15f..ad2645b2351 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -525,89 +525,94 @@ describe('Utils', function () { }); }); - describe('cookie support', function () { - // store original cookie getter and setter so we can reset later - var origCookieSetter = document.__lookupSetter__('cookie'); - var origCookieGetter = document.__lookupGetter__('cookie'); - - // store original cookieEnabled getter and setter so we can reset later - var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); - var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); - - // Replace the document cookie set function with the output of a custom function for testing - let setCookie = (v) => v; - - beforeEach(() => { - // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values - Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { - return { - get: function _get() { - return _value; - }, - set: function _set(v) { - _value = v; - }, - configurable: true - }; - })(window.navigator.cookieEnabled)); - - // Reset the setCookie cookie function before each test - setCookie = (v) => v; - // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled - Object.defineProperty(window.document, 'cookie', (function (_value) { - return { - get: function _get() { - return _value; - }, - set: function _set(v) { - _value = setCookie(v); - }, - configurable: true - }; - })(window.navigator.cookieEnabled)); - }); - - afterEach(() => { - // redefine window.navigator.cookieEnabled to original getter and setter - Object.defineProperty(window.navigator, 'cookieEnabled', { - get: origCookieEnabledGetter, - set: origCookieEnabledSetter, - configurable: true - }); - // redefine document.cookie to original getter and setter - Object.defineProperty(document, 'cookie', { - get: origCookieGetter, - set: origCookieSetter, - configurable: true - }); - }); - - it('should be detected', function() { - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); - }); - - it('should be not available', function() { - setCookie = () => ''; - window.navigator.cookieEnabled = false; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); - }); - - it('should be available', function() { - window.navigator.cookieEnabled = false; - window.document.cookie = 'key=value'; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); - window.navigator.cookieEnabled = false; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); - setCookie = () => ''; - window.navigator.cookieEnabled = true; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); - // Reset the setCookie - setCookie = (v) => v; - }); - }); + /** + * tests fail in IE10 because __lookupSetter__ and __lookupGetter__ are + * not supported. See #1656. commenting out until they can be fixed. + * + * describe('cookie support', function () { + * // store original cookie getter and setter so we can reset later + * var origCookieSetter = document.__lookupSetter__('cookie'); + * var origCookieGetter = document.__lookupGetter__('cookie'); + * + * // store original cookieEnabled getter and setter so we can reset later + * var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); + * var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); + * + * // Replace the document cookie set function with the output of a custom function for testing + * let setCookie = (v) => v; + * + * beforeEach(() => { + * // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values + * Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { + * return { + * get: function _get() { + * return _value; + * }, + * set: function _set(v) { + * _value = v; + * }, + * configurable: true + * }; + * })(window.navigator.cookieEnabled)); + * + * // Reset the setCookie cookie function before each test + * setCookie = (v) => v; + * // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled + * Object.defineProperty(window.document, 'cookie', (function (_value) { + * return { + * get: function _get() { + * return _value; + * }, + * set: function _set(v) { + * _value = setCookie(v); + * }, + * configurable: true + * }; + * })(window.navigator.cookieEnabled)); + * }); + * + * afterEach(() => { + * // redefine window.navigator.cookieEnabled to original getter and setter + * Object.defineProperty(window.navigator, 'cookieEnabled', { + * get: origCookieEnabledGetter, + * set: origCookieEnabledSetter, + * configurable: true + * }); + * // redefine document.cookie to original getter and setter + * Object.defineProperty(document, 'cookie', { + * get: origCookieGetter, + * set: origCookieSetter, + * configurable: true + * }); + * }); + * + * it('should be detected', function() { + * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); + * }); + * + * it('should be not available', function() { + * setCookie = () => ''; + * window.navigator.cookieEnabled = false; + * window.document.cookie = ''; + * assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); + * }); + * + * it('should be available', function() { + * window.navigator.cookieEnabled = false; + * window.document.cookie = 'key=value'; + * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); + * window.navigator.cookieEnabled = false; + * window.document.cookie = ''; + * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); + * setCookie = () => ''; + * window.navigator.cookieEnabled = true; + * window.document.cookie = ''; + * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); + * // Reset the setCookie + * setCookie = (v) => v; + * }); + * }); + **/ describe('delayExecution', function () { it('should execute the core function after the correct number of calls', function () { From 9f4ffdae001ef5b1d55007061d94c3f730047216 Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Tue, 31 Oct 2017 14:33:56 -0700 Subject: [PATCH 43/62] Prebid 0.32.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 062e0943815..b6f05afd29d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.32.0-pre", + "version": "0.32.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5f407913247f15ac2a7b668d0205ce4da0cc9c08 Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Tue, 31 Oct 2017 14:52:04 -0700 Subject: [PATCH 44/62] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6f05afd29d..723c763ab15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.32.0", + "version": "0.33.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 4519cb0ca162ae75439182678e0ce646eb4cc468 Mon Sep 17 00:00:00 2001 From: Tzafrir Ben Ami Date: Wed, 1 Nov 2017 16:11:33 +0200 Subject: [PATCH 45/62] Prebid 1.0 compliant Komoona bidder adapter (#1743) * Prebid 1.0 compliant bidder adapter * PlacementId and hbId support display test banner * Remove aliases * remove check for aliases, breaks build * Add bid response test with mandatory params * change #1742 (https://github.com/prebid/Prebid.js/issues/1742): rather than interpretResponse(body), the bidderFactory is calling your adapter with interpretResponse({ body: body, ... }) * replace describe with it --- modules/komoonaBidAdapter.js | 204 +++++++------- modules/komoonaBidAdapter.md | 29 ++ test/spec/modules/komoonaBidAdapter_spec.js | 284 ++++++++++---------- 3 files changed, 281 insertions(+), 236 deletions(-) create mode 100644 modules/komoonaBidAdapter.md diff --git a/modules/komoonaBidAdapter.js b/modules/komoonaBidAdapter.js index 7cd3218d927..2a8c8753098 100644 --- a/modules/komoonaBidAdapter.js +++ b/modules/komoonaBidAdapter.js @@ -1,117 +1,121 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'komoona'; const ENDPOINT = '//bidder.komoona.com/v1/GetSBids'; +const USYNCURL = '//s.komoona.com/sync/usync.html'; + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: bid => { + return !!(bid && bid.params && bid.params.placementId && bid.params.hbid); + }, + /** + * 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: validBidRequests => { + const tags = validBidRequests.map(bid => { + // map each bid id to bid object to retrieve adUnit code in callback + let tag = { + uuid: bid.bidId, + sizes: bid.sizes, + trid: bid.transactionId, + hbid: bid.params.hbid, + placementid: bid.params.placementId + }; + + // add floor price if specified (not mandatory) + if (bid.params.floorPrice) { + tag.floorprice = bid.params.floorPrice; + } -function KomoonaAdapter() { - let baseAdapter = new Adapter('komoona'); - let bidRequests = {}; - - /* Prebid executes this function when the page asks to send out bid requests */ - baseAdapter.callBids = function(bidRequest) { - const bids = bidRequest.bids || []; - const tags = bids - .filter(bid => valid(bid)) - .map(bid => { - // map request id to bid object to retrieve adUnit code in callback - bidRequests[bid.bidId] = bid; - - let tag = {}; - tag.sizes = bid.sizes; - tag.uuid = bid.bidId; - tag.placementid = bid.params.placementId; - tag.hbid = bid.params.hbid; + return tag; + }); - return tag; - }); + // Komoona server config + const time = new Date().getTime(); + const kbConf = { + ts_as: time, + hb_placements: [], + hb_placement_bidids: {}, + hb_floors: {}, + cb: _generateCb(time), + tz: new Date().getTimezoneOffset(), + }; + + validBidRequests.forEach(bid => { + kbConf.hdbdid = kbConf.hdbdid || bid.params.hbid; + kbConf.encode_bid = kbConf.encode_bid || bid.params.encode_bid; + kbConf.hb_placement_bidids[bid.params.placementId] = bid.bidId; + if (bid.params.floorPrice) { + kbConf.hb_floors[bid.params.placementId] = bid.params.floorPrice; + } + kbConf.hb_placements.push(bid.params.placementId); + }); + let payload = {}; if (!utils.isEmpty(tags)) { - const payload = JSON.stringify({bids: [...tags]}); - - ajax(ENDPOINT, handleResponse, payload, { - contentType: 'text/plain', - withCredentials: true - }); + payload = { bids: [...tags], kbConf: kbConf }; } - }; - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response) { - let parsed; + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload) + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (response, request) => { + const bidResponses = []; try { - parsed = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - - if (!parsed || parsed.error) { - let errorMessage = `in response for ${baseAdapter.getBidderCode()} adapter`; - if (parsed && parsed.error) { errorMessage += `: ${parsed.error}`; } - utils.logError(errorMessage); - - // signal this response is complete - Object.keys(bidRequests) - .map(bidId => bidRequests[bidId].placementCode) - .forEach(placementCode => { - bidmanager.addBidResponse(placementCode, createBid(STATUS.NO_BID)); + if (response.body && response.body.bids) { + response.body.bids.forEach(bid => { + // The bid ID. Used to tie this bid back to the request. + bid.requestId = bid.uuid; + // The creative payload of the returned bid. + bid.ad = bid.creative; + bidResponses.push(bid); }); - - return; - } - - parsed.bids.forEach(tag => { - let status; - if (tag.cpm > 0 && tag.creative) { - status = STATUS.GOOD; - } else { - status = STATUS.NO_BID; } - - tag.bidId = tag.uuid; // bidfactory looks for bidId on requested bid - const bid = createBid(status, tag); - const placement = bidRequests[bid.adId].placementCode; - - bidmanager.addBidResponse(placement, bid); - }); - } - - /* Check that a bid has required paramters */ - function valid(bid) { - if (bid.params.placementId && bid.params.hbid) { - return bid; - } else { - utils.logError('bid requires placementId and hbid params'); + } catch (error) { + utils.logError(error); } - } - - /* Create and return a bid object based on status and tag */ - function createBid(status, tag) { - let bid = bidfactory.createBid(status, tag); - bid.code = baseAdapter.getBidderCode(); - bid.bidderCode = baseAdapter.getBidderCode(); - - if (status === STATUS.GOOD) { - bid.cpm = tag.cpm; - bid.width = tag.width; - bid.height = tag.height; - bid.ad = tag.creative; + return bidResponses; + }, + /** + * Register User Sync. + */ + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USYNCURL + }]; } - - return bid; } - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - }); +}; + +/** +* Generated cache baster value to be sent to bid server +* @param {*} time current time to use for creating cb. +*/ +function _generateCb(time) { + return Math.floor((time % 65536) + (Math.floor(Math.random() * 65536) * 65536)); } -adaptermanager.registerBidAdapter(new KomoonaAdapter(), 'komoona'); - -module.exports = KomoonaAdapter; +registerBidder(spec); diff --git a/modules/komoonaBidAdapter.md b/modules/komoonaBidAdapter.md new file mode 100644 index 00000000000..6f88c19dfa6 --- /dev/null +++ b/modules/komoonaBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +**Module Name**: Komoona Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: support@komoona.com + +# Description + +Connects to Komoona demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'komoona', + params: { + placementId: 'e69148e0ba6c4c07977dc2daae5e1577', + hbid: '1f5b2c10e66e419580bd943b9af692ab', + floorPrice: 0.5 + } + }] + }]; +``` + + diff --git a/test/spec/modules/komoonaBidAdapter_spec.js b/test/spec/modules/komoonaBidAdapter_spec.js index 2657c658ba2..82edba28d03 100644 --- a/test/spec/modules/komoonaBidAdapter_spec.js +++ b/test/spec/modules/komoonaBidAdapter_spec.js @@ -1,152 +1,164 @@ import { expect } from 'chai'; -import Adapter from 'modules/komoonaBidAdapter'; -import bidmanager from 'src/bidmanager'; +import { spec } from 'modules/komoonaBidAdapter'; -const ENDPOINT = '//bidder.komoona.com/v1/GetSBids'; - -const REQUEST = { - 'bidderCode': 'komoona', - 'requestId': '1f43cc36a6a7e', - 'bidderRequestId': '25392d757fad47', - 'bids': [ +describe('Komoona.com Adapter Tests', () => { + const bidsRequest = [ { - 'bidder': 'komoona', - 'params': { - 'hbid': 'abcd666dcba', - 'placementId': 'abcd123123dcba' + bidder: 'komoona', + params: { + placementId: '170577', + hbid: 'abc12345678', }, - 'placementCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [ + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', + sizes: [ [300, 250] ], - 'bidId': '30e5e911c00703', - 'bidderRequestId': '25392d757fad47', - 'requestId': '1f43cc36a6a7e' - } - ], - 'start': 1466493146527 -}; - -const RESPONSE = { - 'bids': [ + bidId: '2faedf1095f815', + bidderRequestId: '18065867f8ae39', + requestId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' + }, { - 'placementid': 'abcd123123dcba', - 'uuid': '30e5e911c00703', - 'width': 728, - 'height': 90, - 'cpm': 0.5, - 'creative': '' + bidder: 'komoona', + params: { + placementId: '281277', + hbid: 'abc12345678', + floorPrice: 0.5 + }, + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', + sizes: [ + [728, 90] + ], + bidId: '3c34e2367a3f59', + bidderRequestId: '18065867f8ae39', + requestId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' + }]; + + const bidsResponse = { + body: { + bids: [ + { + placementid: '170577', + uuid: '2faedf1095f815', + width: 300, + height: 250, + cpm: 0.51, + creative: '', + ttl: 360, + currency: 'USD', + netRevenue: true, + creativeId: 'd30b58c2ba' + } + ] } - ] -}; - -describe('komoonaAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; - let pbConfig; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - pbConfig = REQUEST; - // just a single slot - pbConfig.bids = [pbConfig.bids[0]]; - }); - - afterEach(() => xhr.restore()); - - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('requires paramters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; - }); - - it('requires placementid and hbid', () => { - let backup = pbConfig.bids[0].params; - pbConfig.bids[0].params = {placementid: 1234}; // no hbid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; + }; - pbConfig.bids[0].params = {hbid: 1234}; // no placementid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; - - pbConfig.bids[0].params = backup; - }); - - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(pbConfig); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('POST'); - }); + it('Verifies komoonaAdapter bidder code', () => { + expect(spec.code).to.equal('komoona'); }); - describe('response handler', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); - - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - }); - - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 0.5); - }); - - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'bids': [{ - 'cpm': 0, - 'creative': '', - 'uuid': '30e5e911c00703' - }] - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); + it('Verifies komoonaAdapter bid request validation', () => { + expect(spec.isBidRequestValid(bidsRequest[0])).to.equal(true); + expect(spec.isBidRequestValid(bidsRequest[1])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { hbid: 12345 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { placementid: 12345 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890 } })).to.equal(true); + expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890, floorPrice: 0.8 } })).to.equal(true); + }); - it('handles JSON.parse errors', () => { - server.respondWith(''); + it('Verify komoonaAdapter build request', () => { + var startTime = new Date().getTime(); + + const request = spec.buildRequests(bidsRequest); + expect(request.url).to.equal('//bidder.komoona.com/v1/GetSBids'); + expect(request.method).to.equal('POST'); + const requestData = JSON.parse(request.data); + + // bids object + let bids = requestData.bids; + expect(bids).to.have.lengthOf(2); + + // first bid request: no floor price + expect(bids[0].uuid).to.equal('2faedf1095f815'); + expect(bids[0].floorprice).to.be.undefined; + expect(bids[0].placementid).to.equal('170577'); + expect(bids[0].hbid).to.equal('abc12345678'); + expect(bids[0].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); + expect(bids[0].sizes).to.have.lengthOf(1); + expect(bids[0].sizes[0][0]).to.equal(300); + expect(bids[0].sizes[0][1]).to.equal(250); + + // second bid request: with floor price + expect(bids[1].uuid).to.equal('3c34e2367a3f59'); + expect(bids[1].floorprice).to.equal(0.5); + expect(bids[1].placementid).to.equal('281277'); + expect(bids[1].hbid).to.equal('abc12345678'); + expect(bids[1].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); + expect(bids[1]).to.have.property('sizes') + .that.is.an('array') + .of.length(1) + .that.deep.equals([[728, 90]]); + + // kbConf object + let kbConf = requestData.kbConf; + expect(kbConf.hdbdid).to.equal(bids[0].hbid); + expect(kbConf.hdbdid).to.equal(bids[1].hbid); + expect(kbConf.encode_bid).to.be.undefined; + // kbConf timezone and cb + expect(kbConf.cb).not.to.be.undefined; + expect(kbConf.ts_as).to.be.above(startTime - 1); + expect(kbConf.tz).to.equal(new Date().getTimezoneOffset()); + // kbConf bid ids + expect(kbConf.hb_placement_bidids) + .to.have.property(bids[0].placementid) + .that.equal(bids[0].uuid); + expect(kbConf.hb_placement_bidids) + .to.have.property(bids[1].placementid) + .that.equal(bids[1].uuid); + // kbConf floor price + expect(kbConf.hb_floors).not.to.have.property(bids[0].placementid) + expect(kbConf.hb_floors).to.have.property(bids[1].placementid).that.equal(bids[1].floorprice); + // kbConf placement ids + expect(kbConf.hb_placements).to.have.lengthOf(2); + expect(kbConf.hb_placements[0]).to.equal(bids[0].placementid); + expect(kbConf.hb_placements[1]).to.equal(bids[1].placementid); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('Verify komoonaAdapter build response', () => { + const request = spec.buildRequests(bidsRequest); + const bids = spec.interpretResponse(bidsResponse, request); + + // 'server' return single bid + expect(bids).to.have.lengthOf(1); + + // verify bid object + const bid = bids[0]; + const responseBids = bidsResponse.body.bids; + + expect(bid.cpm).to.equal(responseBids[0].cpm); + expect(bid.ad).to.equal(responseBids[0].creative); + expect(bid.requestId).equal(responseBids[0].uuid); + expect(bid.uuid).equal(responseBids[0].uuid); + expect(bid.width).to.equal(responseBids[0].width); + expect(bid.height).to.equal(responseBids[0].height); + expect(bid.ttl).to.equal(responseBids[0].ttl); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.creativeId).to.equal(responseBids[0].creativeId); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); + it('Verifies komoonaAdapter sync options', () => { + // user sync disabled + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + // user sync enabled + const options = spec.getUserSyncs({ iframeEnabled: true }); + expect(options).to.not.be.undefined; + expect(options).to.have.lengthOf(1); + expect(options[0].type).to.equal('iframe'); + expect(options[0].url).to.equal('//s.komoona.com/sync/usync.html'); }); }); From d818de488b3955bacb1462f532e511dcee02d40e Mon Sep 17 00:00:00 2001 From: lntho Date: Wed, 1 Nov 2017 15:36:54 -0700 Subject: [PATCH 46/62] OpenX Adapter update to Prebid v1.0 (#1714) * OpenX Adapter update to Prebid v1.0 * Updated the ad unit in the OpenX md file * Updated the test ad unit again to something that should work --- modules/openxBidAdapter.js | 472 ++++++++++------------ modules/openxBidAdapter.md | 30 ++ test/spec/modules/openxBidAdapter_spec.js | 431 +++++++++----------- 3 files changed, 438 insertions(+), 495 deletions(-) create mode 100644 modules/openxBidAdapter.md diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 1b9766553c2..a0bcd2a945f 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,298 +1,268 @@ import { config } from 'src/config'; -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const ajax = require('src/ajax'); -const CONSTANTS = require('src/constants.json'); -const utils = require('src/utils.js'); -const adaptermanager = require('src/adaptermanager'); - -const OpenxAdapter = function OpenxAdapter() { - const BIDDER_CODE = 'openx'; - const BIDDER_CONFIG = 'hb_pb'; - const BIDDER_VERSION = '1.0.1'; - let startTime; - let timeout = config.getConfig('bidderTimeout'); - let shouldSendBoPixel = true; +import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; +import {userSync} from 'src/userSync'; +import { BANNER } from 'src/mediaTypes'; + +const SUPPORTED_AD_TYPES = [BANNER]; +const BIDDER_CODE = 'openx'; +const BIDDER_CONFIG = 'hb_pb'; +const BIDDER_VERSION = '2.0.0'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bid) { + return !!(bid.params.unit && bid.params.delDomain); + }, + buildRequests: function(bids) { + let isIfr = utils.inIframe(); + let currentURL = (window.parent !== window) ? document.referrer : window.location.href; + if (bids.length === 0) { + return; + } - let pdNode = null; + let delDomain = bids[0].params.delDomain; + let configuredBc = bids[0].params.bc; + let bc = configuredBc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`; - function oxARJResponse (oxResponseObj) { - try { - oxResponseObj = JSON.parse(oxResponseObj); - } catch (_) { - // Could not parse response, changing to an empty response instead - oxResponseObj = { - ads: {} - }; - } + return buildOXRequest(bids, { + ju: currentURL, + jr: currentURL, + ch: document.charSet || document.characterSet, + res: `${screen.width}x${screen.height}x${screen.colorDepth}`, + ifr: isIfr, + tz: new Date().getTimezoneOffset(), + tws: getViewportDimensions(isIfr), + ef: 'bt%2Cdb', + be: 1, + bc: bc, + nocache: new Date().getTime() + }, + delDomain); + }, + interpretResponse: function({body: oxResponseObj}, bidRequest) { + let bidResponses = []; let adUnits = oxResponseObj.ads.ad; if (oxResponseObj.ads && oxResponseObj.ads.pixels) { - makePDCall(oxResponseObj.ads.pixels); + userSync.registerSync('iframe', BIDDER_CODE, oxResponseObj.ads.pixels); } - if (!adUnits) { adUnits = []; } + bidResponses = createBidResponses(adUnits, bidRequest.payload); + return bidResponses; + } +}; - let bids = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'openx').bids; - for (let i = 0; i < bids.length; i++) { - let bid = bids[i]; - let auid = null; - let adUnit = null; - // find the adunit in the response - for (let j = 0; j < adUnits.length; j++) { - adUnit = adUnits[j]; - if (String(bid.params.unit) === String(adUnit.adunitid) && adUnitHasValidSizeFromBid(adUnit, bid) && !adUnit.used) { - auid = adUnit.adunitid; +function createBidResponses(adUnits, {bids, startTime}) { + let bidResponses = []; + let shouldSendBoPixel = bids[0].params.sendBoPixel; + if (shouldSendBoPixel === undefined) { + // Not specified, default to turned on + shouldSendBoPixel = true; + } + for (let i = 0; i < adUnits.length; i++) { + let adUnit = adUnits[i]; + let bidResponse = {}; + if (adUnits.length == bids.length) { + // request and response length match, directly assign the request id based on positioning + bidResponse.requestId = bids[i].bidId; + } else { + for (let j = i; j < bids.length; j++) { + let bid = bids[j]; + if (String(bid.params.unit) === String(adUnit.adunitid) && adUnitHasValidSizeFromBid(adUnit, bid) && !bid.matched) { + // ad unit and size match, this is the correct bid response to bid + bidResponse.requestId = bid.bidId; + bid.matched = true; break; } } - - let beaconParams = { - bd: +(new Date()) - startTime, - br: '0', // may be 0, t, or p - bt: Math.min(timeout, window.PREBID_TIMEOUT || config.getConfig('bidderTimeout')), - bs: window.location.hostname - }; - // no fill :( - if (!auid || !adUnit.pub_rev) { - addBidResponse(null, bid); - continue; - } - adUnit.used = true; - - beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; - beaconParams.bp = adUnit.pub_rev; - beaconParams.ts = adUnit.ts; - addBidResponse(adUnit, bid); - if (shouldSendBoPixel === true) { - buildBoPixel(adUnit.creative[0], beaconParams); - } } - }; - - function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; - } - docEl = tDoc.documentElement; - body = tDoc.body; - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + if (adUnit.pub_rev) { + bidResponse.cpm = Number(adUnit.pub_rev) / 1000; } else { - docEl = tDoc.documentElement; - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; + // No fill, do not add the bidresponse + continue; } - - return `${width}x${height}`; - } - - function makePDCall(pixelsUrl) { - let pdFrame = utils.createInvisibleIframe(); - let name = 'openx-pd'; - pdFrame.setAttribute('id', name); - pdFrame.setAttribute('name', name); - let rootNode = document.body; - - if (!rootNode) { - return; + let creative = adUnit.creative[0]; + if (creative) { + bidResponse.width = creative.width; + bidResponse.height = creative.height; } - - pdFrame.src = pixelsUrl; - - if (pdNode) { - pdNode.parentNode.replaceChild(pdFrame, pdNode); - pdNode = pdFrame; - } else { - pdNode = rootNode.appendChild(pdFrame); + bidResponse.creativeId = creative.id; + bidResponse.ad = adUnit.html; + if (adUnit.deal_id) { + bidResponse.dealId = adUnit.deal_id; } - } - - function addBidResponse(adUnit, bid) { - let bidResponse = bidfactory.createBid(adUnit ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID, bid); - bidResponse.bidderCode = BIDDER_CODE; - - if (adUnit) { - let creative = adUnit.creative[0]; - bidResponse.ad = adUnit.html; - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - bidResponse.ad_id = adUnit.adid; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; - } - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; - } - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; - } + // default 5 mins + bidResponse.ttl = 300; + // true is net, false is gross + bidResponse.netRevenue = true; + bidResponse.currency = adUnit.currency; + + // additional fields to add + if (adUnit.tbd) { + bidResponse.tbd = adUnit.tbd; } - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } + bidResponse.ts = adUnit.ts; - function buildQueryStringFromParams(params) { - for (let key in params) { - if (params.hasOwnProperty(key)) { - if (!params[key]) { - delete params[key]; - } - } + let bt = config.getConfig('bidderTimeout'); + if (window.PREBID_TIMEOUT) { + bt = Math.min(window.PREBID_TIMEOUT, bt); + } + let beaconParams = { + bd: +(new Date()) - startTime, + br: '0', // may be 0, t, or p + bt: bt, + bs: window.location.hostname + }; + + beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; + beaconParams.bp = adUnit.pub_rev; + beaconParams.ts = adUnit.ts; + let boUrl; + if (shouldSendBoPixel) { + boUrl = getBoUrl(adUnit.creative[0], beaconParams); + } + if (boUrl) { + userSync.registerSync('image', BIDDER_CODE, boUrl); } - return utils._map(Object.keys(params), key => `${key}=${params[key]}`) - .join('&'); + bidResponses.push(bidResponse); } + return bidResponses; +} - function buildBoPixel(creative, params) { - let img = new Image(); - let recordPixel = creative.tracking.impression; - let boBase = recordPixel.match(/([^?]+\/)ri\?/); +function getBoUrl(creative, params) { + let recordPixel = creative.tracking.impression; + let boBase = recordPixel.match(/([^?]+\/)ri\?/); - if (boBase) { - img.src = `${boBase[1]}bo?${buildQueryStringFromParams(params)}`; - } + if (boBase) { + return `${boBase[1]}bo?${buildQueryStringFromParams(params)}`; } +} - function adUnitHasValidSizeFromBid(adUnit, bid) { - let sizes = utils.parseSizesInput(bid.sizes); - let sizeLength = (sizes && sizes.length) || 0; - let found = false; - let creative = adUnit.creative && adUnit.creative[0]; - let creative_size = String(creative.width) + 'x' + String(creative.height); - - if (utils.isArray(sizes)) { - for (let i = 0; i < sizeLength; i++) { - let size = sizes[i]; - if (String(size) === String(creative_size)) { - found = true; - break; - } +function buildQueryStringFromParams(params) { + for (let key in params) { + if (params.hasOwnProperty(key)) { + if (!params[key]) { + delete params[key]; } } - return found; } - - function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (Array.isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey + '=' + value).replace('+', '.').replace('/', '_') + return utils._map(Object.keys(params), key => `${key}=${params[key]}`) + .join('&'); +} + +function adUnitHasValidSizeFromBid(adUnit, bid) { + let sizes = utils.parseSizesInput(bid.sizes); + if (!sizes) { + return false; } - - function buildRequest(bids, params, delDomain) { - if (!utils.isArray(bids)) { - return; - } - - params.auid = utils._map(bids, bid => bid.params.unit).join('%2C'); - params.dddid = utils._map(bids, bid => bid.transactionId).join('%2C'); - params.aus = utils._map(bids, bid => { - return utils.parseSizesInput(bid.sizes).join(','); - }).join('|'); - - let customParamsForAllBids = []; - let hasCustomParam = false; - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = utils._map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } - }); - if (hasCustomParam) { - params.tps = customParamsForAllBids.join('%2C'); - } - - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - if (bid.params.customFloor) { - customFloorsForAllBids.push(bid.params.customFloor * 1000); - hasCustomFloor = true; - } else { - customFloorsForAllBids.push(0); + let found = false; + let creative = adUnit.creative && adUnit.creative[0]; + let creative_size = String(creative.width) + 'x' + String(creative.height); + + if (utils.isArray(sizes)) { + for (let i = 0; i < sizes.length; i++) { + let size = sizes[i]; + if (String(size) === String(creative_size)) { + found = true; + break; } - }); - if (hasCustomFloor) { - params.aumfs = customFloorsForAllBids.join('%2C'); - } - - try { - let queryString = buildQueryStringFromParams(params); - let url = `//${delDomain}/w/1.0/arj?${queryString}`; - ajax.ajax(url, oxARJResponse, void (0), { - withCredentials: true - }); - } catch (err) { - utils.logMessage(`Ajax call failed due to ${err}`); } } - - function callBids(params) { - let isIfr; - const bids = params.bids || []; - let currentURL = (window.parent !== window) ? document.referrer : window.location.href; - currentURL = currentURL && encodeURIComponent(currentURL); + return found; +} + +function getViewportDimensions(isIfr) { + let width; + let height; + let tWin = window; + let tDoc = document; + let docEl = tDoc.documentElement; + let body; + + if (isIfr) { try { - isIfr = window.self !== window.top; + tWin = window.top; + tDoc = window.top.document; } catch (e) { - isIfr = false; - } - if (bids.length === 0) { return; } + docEl = tDoc.documentElement; + body = tDoc.body; + + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + docEl = tDoc.documentElement; + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; + } - let delDomain = bids[0].params.delDomain; - let bcOverride = bids[0].params.bc; + return `${width}x${height}`; +} - startTime = new Date(params.start); - if (params.timeout) { - timeout = params.timeout; - } - if (bids[0].params.hasOwnProperty('sendBoPixel') && typeof (bids[0].params.sendBoPixel) === 'boolean') { - shouldSendBoPixel = bids[0].params.sendBoPixel; +function formatCustomParms(customKey, customParams) { + let value = customParams[customKey]; + if (utils.isArray(value)) { + // if value is an array, join them with commas first + value = value.join(','); + } + // return customKey=customValue format, escaping + to . and / to _ + return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') +} + +function buildOXRequest(bids, oxParams, delDomain) { + if (!utils.isArray(bids)) { + return; + } + + oxParams.auid = utils._map(bids, bid => bid.params.unit).join(','); + oxParams.dddid = utils._map(bids, bid => bid.transactionId).join(','); + oxParams.aus = utils._map(bids, bid => { + return utils.parseSizesInput(bid.sizes).join(','); + }).join('|'); + + let customParamsForAllBids = []; + let hasCustomParam = false; + bids.forEach(function (bid) { + if (bid.params.customParams) { + let customParamsForBid = utils._map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); + let formattedCustomParams = window.btoa(customParamsForBid.join('&')); + hasCustomParam = true; + customParamsForAllBids.push(formattedCustomParams); + } else { + customParamsForAllBids.push(''); } + }); + if (hasCustomParam) { + oxParams.tps = customParamsForAllBids.join(','); + } - buildRequest(bids, { - ju: currentURL, - jr: currentURL, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isIfr, - tz: startTime.getTimezoneOffset(), - tws: getViewportDimensions(isIfr), - ef: 'bt%2Cdb', - be: 1, - bc: bcOverride || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - nocache: new Date().getTime() - }, - delDomain); + let customFloorsForAllBids = []; + let hasCustomFloor = false; + bids.forEach(function (bid) { + if (bid.params.customFloor) { + customFloorsForAllBids.push(bid.params.customFloor * 1000); + hasCustomFloor = true; + } else { + customFloorsForAllBids.push(0); + } + }); + if (hasCustomFloor) { + oxParams.aumfs = customFloorsForAllBids.join(','); } + let url = `//${delDomain}/w/1.0/arj`; return { - callBids: callBids + method: 'GET', + url: url, + data: oxParams, + payload: {'bids': bids, 'startTime': new Date()} }; -}; - -adaptermanager.registerBidAdapter(new OpenxAdapter(), 'openx'); +} -module.exports = OpenxAdapter; +registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md new file mode 100644 index 00000000000..5b3ad77ce6d --- /dev/null +++ b/modules/openxBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: OpenX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: team-openx@openx.com +``` + +# Description + +Module that connects to OpenX's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[728, 90]], // a display size + bids: [ + { + bidder: "openx", + params: { + unit: "539439964", + delDomain: "se-demo-d.openx.net" + } + } + ] + }, + ]; +``` diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index c08e8c256e6..cee521b2921 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,277 +1,220 @@ -const expect = require('chai').expect; -const assert = require('chai').assert; -const adapter = require('modules/openxBidAdapter')(); -const bidmanager = require('src/bidmanager'); -const adloader = require('src/adloader'); -const CONSTANTS = require('src/constants.json'); -const ajax = require('src/ajax'); +import { expect } from 'chai'; +import { spec } from 'modules/openxBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; -describe('openx adapter tests', function () { - describe('test openx callback response', function () { - let stubAjax; - let stubAddBidResponse; - this.response = null; - let responseHandlerCallback = (_url, callback, _data, _params) => { - return callback(this.response); +const URLBASE = '/w/1.0/arj'; + +describe('OpenxAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', }; - beforeEach(() => { - stubAjax = sinon.stub(ajax, 'ajax', responseHandlerCallback); - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - sinon.stub(document.body, 'appendChild'); + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - afterEach(() => { - stubAjax.restore(); - stubAddBidResponse.restore(); - this.response = null; - document.body.appendChild.restore(); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {'unit': '12345678'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should add empty bid responses if no bids returned', () => { - // empty ads in bidresponse - this.response = JSON.stringify({ - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'http://testpixels.net', - 'ad': [] - } - }); + }); - let bidderRequest = { - bidderCode: 'openx', - bids: [ - { - bidId: 'bidId1', - bidder: 'openx', - params: { - delDomain: 'delDomain1', - unit: '1234' - }, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' - } - ] - }; + describe('buildRequests', () => { + let bidRequests = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }]; + + it('should send bid request to openx url via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + expect(request.method).to.equal('GET'); + }); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.callBids(bidderRequest); + it('should have the correct parameters', () => { + const request = spec.buildRequests(bidRequests); + const dataParams = request.data; - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponse1.bidderCode).to.equal('openx'); + expect(dataParams.auid).to.exist; + expect(dataParams.auid).to.equal('12345678'); + expect(dataParams.aus).to.exist; + expect(dataParams.aus).to.equal('300x250,300x600'); }); - it('should add bid responses if bids are returned', () => { - let bidderRequest = { - bidderCode: 'openx', - bids: [ - { - bidId: 'bidId1', - bidder: 'openx', - params: { - delDomain: 'delDomain1', - unit: '1234' - }, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' - } - ] - }; - - this.response = JSON.stringify({ - 'ads': + it('should send out custom params on bids that have customParams specified', () => { + let bidRequest = Object.assign({}, + bidRequests[0], { - 'version': 1, - 'count': 1, - 'pixels': 'http://testpixels.net', - 'ad': [ - { - 'adunitid': 1234, - 'adid': 5678, - 'type': 'html', - 'html': 'test_html', - 'framed': 1, - 'is_fallback': 0, - 'ts': 'ts', - 'cpipc': 1000, - 'pub_rev': '1000', - 'adv_id': 'adv_id', - 'brand_id': '', - 'creative': [ - { - 'width': '300', - 'height': '250', - 'target': '_blank', - 'mime': 'text/html', - 'media': 'test_media', - 'tracking': { - 'impression': 'test_impression', - 'inview': 'test_inview', - 'click': 'test_click' - } - } - ] - }] + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } } - }); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.callBids(bidderRequest); + ); - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - let bid1width = '300'; - let bid1height = '250'; - let cpm = 1; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse1.bidderCode).to.equal('openx'); - expect(bidResponse1.width).to.equal(bid1width); - expect(bidResponse1.height).to.equal(bid1height); - expect(bidResponse1.cpm).to.equal(cpm); - }); - }); - describe('test openx ad requests', () => { - let spyAjax; - let spyBtoa; - beforeEach(() => { - spyAjax = sinon.spy(ajax, 'ajax'); - spyBtoa = sinon.spy(window, 'btoa'); - sinon.stub(document.body, 'appendChild'); - }); - afterEach(() => { - spyAjax.restore(); - spyBtoa.restore(); - document.body.appendChild.restore(); - }); + const request = spec.buildRequests([bidRequest]); + const dataParams = request.data; - it('should not call ajax when inputting with empty params', () => { - adapter.callBids({}); - assert(!spyAjax.called); + expect(dataParams.tps).to.exist; + expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); }); - it('should call ajax with the correct bid url', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234 - } + it('should send out custom floors on bids that have customFloors specified', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.5 } - ] - }; - adapter.callBids(params); - sinon.assert.calledOnce(spyAjax); + } + ); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600'); + const request = spec.buildRequests([bidRequest]); + const dataParams = request.data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); }); - it('should send out custom params on bids that have customParams specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customParams: {'test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + it('should send out custom bc parameter, if override is present', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'bc': 'hb_override' } - ] - }; - adapter.callBids(params); + } + ); - sinon.assert.calledOnce(spyAjax); - sinon.assert.calledWith(spyBtoa, 'test1=testval1.&test2=testval2_,testval3'); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600'); + const request = spec.buildRequests([bidRequest]); + const dataParams = request.data; + + expect(dataParams.bc).to.exist; + expect(dataParams.bc).to.equal('hb_override'); }); + }); - it('should send out custom floors on bids that have customFloors specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customFloor: 1 - } - }, - { - sizes: [[320, 50]], - params: { - delDomain: 'testdelDomain', - unit: 1234 - } - }, + describe('interpretResponse', () => { + let bids = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }]; + let bidRequest = { + method: 'GET', + url: 'url', + data: {}, + payload: {'bids': bids, 'startTime': new Date()} + }; + let bidResponse = { + 'ads': + { + 'version': 1, + 'count': 1, + 'pixels': 'http://testpixels.net', + 'ad': [ { - sizes: [[728, 90]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customFloor: 1.5 - } - } - ] - }; - adapter.callBids(params); + 'adunitid': 12345678, + 'adid': 5678, + 'type': 'html', + 'html': 'test_html', + 'framed': 1, + 'is_fallback': 0, + 'ts': 'ts', + 'cpipc': 1000, + 'pub_rev': '1000', + 'adv_id': 'adv_id', + 'brand_id': '', + 'creative': [ + { + 'width': '300', + 'height': '250', + 'target': '_blank', + 'mime': 'text/html', + 'media': 'test_media', + 'tracking': { + 'impression': 'test_impression', + 'inview': 'test_inview', + 'click': 'test_click' + } + } + ] + }] + } + }; + it('should return correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 1, + 'width': '300', + 'height': '250', + 'creativeId': 5678, + 'ad': 'test_html', + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD', + 'ts': 'ts' + } + ]; - sinon.assert.calledOnce(spyAjax); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600|320x50|728x90'); - expect(bidUrl).to.include('aumfs=1000%2C0%2C1500'); + let result = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(Object.keys(result[0])).to.eql(Object.keys(expectedResponse[0])); }); - it('should change bc param if configureable bc is specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' - } - }, - { - sizes: [[320, 50]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' - } - }, - { - sizes: [[728, 90]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' - } - } - ] + it('handles nobid responses', () => { + bidResponse = { + 'ads': + { + 'version': 1, + 'count': 1, + 'pixels': 'http://testpixels.net', + 'ad': [] + } }; - adapter.callBids(params); - sinon.assert.calledOnce(spyAjax); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600|320x50|728x90'); - expect(bidUrl).to.include('bc=hb_pb_test'); + let result = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(result.length).to.equal(0); }); }); }); From bca5b16e6cc125654280a15f8611209319e7d168 Mon Sep 17 00:00:00 2001 From: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Date: Fri, 3 Nov 2017 16:19:53 +0100 Subject: [PATCH 47/62] updated adxcg adapter for prebid 1.0 (#1798) --- modules/adxcgBidAdapter.js | 6 +++--- test/spec/modules/adxcgBidAdapter_spec.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 9073a17bda3..0a722000b55 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -57,7 +57,7 @@ export const spec = { pathname: '/get/adi', search: { renderformat: 'javascript', - ver: 'r20171019PB10', + ver: 'r20171102PB10', adzoneid: adZoneIds.join(','), format: sizes.join(','), prebidBidIds: prebidBidIds.join(','), @@ -90,9 +90,9 @@ export const spec = { bid.requestId = serverResponseOneItem.bidId; bid.cpm = serverResponseOneItem.cpm; bid.creativeId = parseInt(serverResponseOneItem.creativeId); - bid.currency = 'USD'; + bid.currency = serverResponseOneItem.currency ? serverResponseOneItem.currency : 'USD'; bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true; - bid.ttl = 300; + bid.ttl = serverResponseOneItem.ttl ? serverResponseOneItem.ttl : 300; if (serverResponseOneItem.deal_id != null && serverResponseOneItem.deal_id.trim().length > 0) { bid.dealId = serverResponseOneItem.deal_id; diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index dbf7359e98d..afb58361ba1 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -54,7 +54,7 @@ describe('AdxcgAdapter', () => { let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20171019PB10'); + expect(query.ver).to.equal('r20171102PB10'); expect(query.source).to.equal('pbjs10'); expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('1'); From a1678d3f17aa0a67f5b35727c58b25013e384a0c Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 3 Nov 2017 15:30:12 -0700 Subject: [PATCH 48/62] Update sharethrough bid adapter (#1740) * Update sharethrough bid adapter * add md for sharethroughBidAdpapter * remove bidderCode and parse response * remove linting errors --- modules/sharethroughBidAdapter.js | 190 ++++------ modules/sharethroughBidAdapter.md | 40 +++ .../modules/sharethroughBidAdapter_spec.js | 328 ++++++------------ 3 files changed, 214 insertions(+), 344 deletions(-) create mode 100644 modules/sharethroughBidAdapter.md diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index d53fb0d92db..ef0cf9619c1 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,128 +1,70 @@ -var utils = require('src/utils.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var ajax = require('src/ajax.js').ajax; -var adaptermanager = require('src/adaptermanager'); - -const STR_BIDDER_CODE = 'sharethrough'; -const STR_VERSION = '1.2.0'; - -var SharethroughAdapter = function SharethroughAdapter() { - const str = {}; - str.STR_BTLR_HOST = document.location.protocol + '//btlr.sharethrough.com'; - str.STR_BEACON_HOST = document.location.protocol + '//b.sharethrough.com/butler?'; - str.placementCodeSet = {}; - str.ajax = ajax; - - function _callBids(params) { - const bids = params.bids; - - // cycle through bids - for (let i = 0; i < bids.length; i += 1) { - const bidRequest = bids[i]; - str.placementCodeSet[bidRequest.placementCode] = bidRequest; - const scriptUrl = _buildSharethroughCall(bidRequest); - str.ajax(scriptUrl, _createCallback(bidRequest), undefined, {withCredentials: true}); - } +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'sharethrough'; +const VERSION = '2.0.0'; +const STR_ENDPOINT = document.location.protocol + '//btlr.sharethrough.com/header-bid/v1'; + +export const sharethroughAdapterSpec = { + code: BIDDER_CODE, + isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, + buildRequests: (bidRequests) => { + return bidRequests.map(bid => { + return { + method: 'GET', + url: STR_ENDPOINT, + data: { + bidId: bid.bidId, + placement_key: bid.params.pkey, + hbVersion: '$prebid.version$', + strVersion: VERSION, + hbSource: 'prebid' + } + }; + }) + }, + interpretResponse: ({ body }, req) => { + if (!Object.keys(body).length) return []; + + const creative = body.creatives[0]; + + return [{ + requestId: req.data.bidId, + width: 0, + height: 0, + cpm: creative.cpm, + creativeId: creative.creative.creative_key, + deal_id: creative.creative.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 360, + ad: generateAd(body, req) + }]; } - - function _createCallback(bidRequest) { - return (bidResponse) => { - _strcallback(bidRequest, bidResponse); - }; - } - - function _buildSharethroughCall(bid) { - const pkey = utils.getBidIdParameter('pkey', bid.params); - - let host = str.STR_BTLR_HOST; - - let url = host + '/header-bid/v1?'; - url = utils.tryAppendQueryString(url, 'bidId', bid.bidId); - url = utils.tryAppendQueryString(url, 'placement_key', pkey); - url = appendEnvFields(url); - - return url; - } - - function _strcallback(bidObj, bidResponse) { - try { - bidResponse = JSON.parse(bidResponse); - } catch (e) { - _handleInvalidBid(bidObj); - return; - } - - if (bidResponse.creatives && bidResponse.creatives.length > 0) { - _handleBid(bidObj, bidResponse); - } else { - _handleInvalidBid(bidObj); - } - } - - function _handleBid(bidObj, bidResponse) { - try { - const bidId = bidResponse.bidId; - const bid = bidfactory.createBid(1, bidObj); - bid.bidderCode = STR_BIDDER_CODE; - bid.cpm = bidResponse.creatives[0].cpm; - const size = bidObj.sizes[0]; - bid.width = size[0]; - bid.height = size[1]; - bid.adserverRequestId = bidResponse.adserverRequestId; - str.placementCodeSet[bidObj.placementCode].adserverRequestId = bidResponse.adserverRequestId; - - bid.pkey = utils.getBidIdParameter('pkey', bidObj.params); - - const windowLocation = `str_response_${bidId}`; - const bidJsonString = JSON.stringify(bidResponse); - bid.ad = `
-
- - ` +} + +function generateAd(body, req) { + const strRespId = `str_response_${req.data.bidId}`; + + return ` +
+
+ + + ` - bid.ad += sfpScriptTag; + const sfp_js = document.createElement('script'); + sfp_js.src = "//native.sharethrough.com/assets/sfp.js"; + sfp_js.type = 'text/javascript'; + sfp_js.charset = 'utf-8'; + try { + window.top.document.getElementsByTagName('body')[0].appendChild(sfp_js); + } catch (e) { + console.log(e); + } } - bidmanager.addBidResponse(bidObj.placementCode, bid); - } catch (e) { - _handleInvalidBid(bidObj); - } - } - - function _handleInvalidBid(bidObj) { - const bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = STR_BIDDER_CODE; - bidmanager.addBidResponse(bidObj.placementCode, bid); - } - - function appendEnvFields(url) { - url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); - url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION); - url = utils.tryAppendQueryString(url, 'hbSource', 'prebid'); - - return url; - } - - return { - callBids: _callBids, - str: str, - }; -}; - -adaptermanager.registerBidAdapter(new SharethroughAdapter(), 'sharethrough'); + })() + `; +} -module.exports = SharethroughAdapter; +registerBidder(sharethroughAdapterSpec); diff --git a/modules/sharethroughBidAdapter.md b/modules/sharethroughBidAdapter.md new file mode 100644 index 00000000000..8ab44f2a0f2 --- /dev/null +++ b/modules/sharethroughBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Sharethrough Bidder Adapter +Module Type: Bidder Adapter +Maintainer: jchau@sharethrough.com && cpan@sharethrough.com +``` + +# Description + +Module that connects to Sharethrough's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[1, 1]], // a display size + bids: [ + { + bidder: "sharethrough", + params: { + pkey: 'LuB3vxGGFrBZJa6tifXW4xgK' + } + } + ] + },{ + code: 'test-div', + sizes: [[1, 1]], // a mobile size + bids: [ + { + bidder: "sharethrough", + params: { + pkey: 'LuB3vxGGFrBZJa6tifXW4xgK' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index b92dfe4d493..fdac8a84d8d 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,239 +1,127 @@ import { expect } from 'chai'; -import Adapter from '../../../modules/sharethroughBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import bidfactory from '../../../src/bidfactory'; - -describe('sharethrough adapter', () => { - let adapter; - let sandbox; - let bidsRequestedOriginal; +import { sharethroughAdapterSpec } from 'modules/sharethroughBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const spec = newBidder(sharethroughAdapterSpec).getSpec(); +const bidderRequest = [ + { + bidder: 'sharethrough', + bidId: 'bidId1', + sizes: [[600, 300]], + placementCode: 'foo', + params: { + pkey: 'aaaa1111' + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId2', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'bbbb2222' + } + }]; +const prebidRequest = [{ + method: 'GET', + url: document.location.protocol + '//btlr.sharethrough.com' + '/header-bid/v1', + data: { + bidId: 'bidId', + placement_key: 'pKey' + } +}]; +const bidderResponse = { + body: { + 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', + 'bidId': 'bidId1', + 'version': 1, + 'creatives': [{ + 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', + 'cpm': 12.34, + 'creative': { + 'deal_id': 'aDealId', + 'creative_key': 'aCreativeId' + } + }], + 'stxUserId': '' + }, + header: { get: (header) => header } +}; + +describe('sharethrough adapter spec', () => { + describe('.code', () => { + it('should return a bidder code of sharethrough', () => { + expect(spec.code).to.eql('sharethrough'); + }); + }) - const bidderRequest = { - bidderCode: 'sharethrough', - bids: [ - { - bidder: 'sharethrough', - bidId: 'bidId1', - sizes: [[600, 300]], - placementCode: 'foo', - params: { - pkey: 'aaaa1111' - } - }, - { + describe('.isBidRequestValid', () => { + it('should return false if req has no pkey', () => { + const invalidBidRequest = { bidder: 'sharethrough', - bidId: 'bidId2', - sizes: [[700, 400]], - placementCode: 'bar', params: { - pkey: 'bbbb2222' + notPKey: 'abc123' } - } - ] - }; - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids', () => { - let firstBidUrl; - let secondBidUrl; - - beforeEach(() => { - sandbox.spy(adapter.str, 'ajax'); - }); - - it('should call ajax to make a request for each bid', () => { - adapter.callBids(bidderRequest); - - firstBidUrl = adapter.str.ajax.firstCall.args[0]; - secondBidUrl = adapter.str.ajax.secondCall.args[0]; - - sinon.assert.calledTwice(adapter.str.ajax); - - expect(firstBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId1&placement_key=aaaa1111&hbVersion=%24prebid.version%24&strVersion=1.2.0&hbSource=prebid&'); - expect(secondBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId2&placement_key=bbbb2222&hbVersion=%24prebid.version%24&strVersion=1.2.0&hbSource=prebid&'); - }); - }); - - describe('bid requests', () => { - let firstBid; - let secondBid; - let server; - let stubAddBidResponse; - let stubCreateBid; - - beforeEach(() => { - stubAddBidResponse = sandbox.stub(bidManager, 'addBidResponse'); - server = sinon.fakeServer.create(); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - adapter.str.placementCodeSet['bar'] = {}; - // respond - - let bidderResponse1 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId1', - 'creatives': [ - { - 'cpm': 12.34, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' - }; - - let bidderResponse2 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId2', - 'creatives': [ - { - 'cpm': 12.35, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' }; - - server.respondWith(/aaaa1111/, JSON.stringify(bidderResponse1)); - server.respondWith(/bbbb2222/, JSON.stringify(bidderResponse2)); - adapter.callBids(bidderRequest); - - server.respond(); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; + expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - afterEach(() => { - server.restore(); - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should include the bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(1); - expect(secondBid.getStatusCode()).to.eql(1); - }); - - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 12.34); - expect(secondBid).to.have.property('cpm', 12.35); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'sharethrough'); - expect(secondBid).to.have.property('bidderCode', 'sharethrough'); + it('should return false if req has wrong bidder code', () => { + const invalidBidRequest = { + bidder: 'notSharethrough', + params: { + notPKey: 'abc123' + } + }; + expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should include the ad on the bid object', () => { - expect(firstBid).to.have.property('ad'); - expect(secondBid).to.have.property('ad'); - }); + it('should return true if req is correct', () => { + expect(spec.isBidRequestValid(bidderRequest[0])).to.eq(true); + expect(spec.isBidRequestValid(bidderRequest[1])).to.eq(true); + }) + }); - it('should include the size on the bid object', () => { - expect(firstBid).to.have.property('width', 600); - expect(firstBid).to.have.property('height', 300); - expect(secondBid).to.have.property('width', 700); - expect(secondBid).to.have.property('height', 400); - }); + describe('.buildRequests', () => { + it('should return an array of requests', () => { + const bidRequests = spec.buildRequests(bidderRequest); - it('should include the pkey', () => { - expect(firstBid).to.have.property('pkey', 'aaaa1111'); - expect(secondBid).to.have.property('pkey', 'bbbb2222'); + expect(bidRequests[0].url).to.eq( + 'http://btlr.sharethrough.com/header-bid/v1'); + expect(bidRequests[1].url).to.eq( + 'http://btlr.sharethrough.com/header-bid/v1') + expect(bidRequests[0].method).to.eq('GET'); }); + }); - describe('when bidResponse string cannot be JSON parsed', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - - server.respondWith(/aaaa1111/, 'non JSON string'); - adapter.callBids(bidderRequest); - - server.respond(); - }); - - afterEach(() => { - server.restore(); - stubAddBidResponse.reset(); - }); - - it('should add a bid response', () => { - sinon.assert.called(bidManager.addBidResponse); - }); - - it('should set bidder code on invalid bid response', () => { - let bidResponse = bidManager.addBidResponse.firstCall.args[1] - expect(bidResponse).to.have.property('bidderCode', 'sharethrough') - }); + describe('.interpretResponse', () => { + it('returns a correctly parsed out response', () => { + expect(spec.interpretResponse(bidderResponse, prebidRequest[0])[0]).to.include( + { + width: 0, + height: 0, + cpm: 12.34, + creativeId: 'aCreativeId', + deal_id: 'aDealId', + currency: 'USD', + netRevenue: true, + ttl: 360, + }); }); - describe('when no fill', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - - let bidderResponse1 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId1', - 'creatives': [ - { - 'cpm': 12.34, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' - }; - - server.respondWith(/aaaa1111/, JSON.stringify(bidderResponse1)); - adapter.callBids(bidderRequest); - - server.respond(); - }); - - afterEach(() => { - server.restore(); - stubAddBidResponse.reset(); - }); - - it('should add a bid response', () => { - sinon.assert.called(bidManager.addBidResponse); - }); - - it('should set bidder code on invalid bid response', () => { - let bidResponse = bidManager.addBidResponse.firstCall.args[1] - expect(bidResponse).to.have.property('bidderCode', 'sharethrough') - }); + it('correctly sends back a sfp script tag', () => { + const adMarkup = spec.interpretResponse(bidderResponse, prebidRequest[0])[0].ad; + const resp = btoa(JSON.stringify(bidderResponse)); + + expect(adMarkup).to.match( + /data-str-native-key="pKey" data-stx-response-name=\"str_response_bidId\"/); + expect(!!adMarkup.indexOf(resp)).to.eql(true); + expect(adMarkup).to.match( + /'; + url += ';traffic_info=' + encodeURIComponent(JSON.stringify(getUrlVars())); + if (bidParam.params.subId) { + url += ';subid=' + encodeURIComponent(bidParam.params.subId); } + return ''; +} - function _getUrlVars() { - var vars = {}; - var hash; - var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); - for (var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - if (!hash[0].match(/^utm/)) { - continue; - } +function getUrlVars() { + var vars = {}; + var hash; + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); + for (var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + if (hash[0].match(/^utm_/)) { vars[hash[0]] = hash[1].substr(0, 150); } - return vars; } - - return { - callBids: _callBids - }; + return vars; } -adaptermanager.registerBidAdapter(new UnderdogMediaAdapter(), 'underdogmedia'); - -module.exports = UnderdogMediaAdapter; +registerBidder(spec); diff --git a/modules/underdogmediaBidAdapter.md b/modules/underdogmediaBidAdapter.md new file mode 100644 index 00000000000..f652e2fcbbf --- /dev/null +++ b/modules/underdogmediaBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +**Module Name**: Underdog Media Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: jake@underdogmedia.com + +# Description + +Module that connects to Underdog Media's servers to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "underdogmedia", + params: { + siteId: '12143' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 249111be6ea..1e7a80aaff8 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -1,122 +1,241 @@ -import Adapter from '../../../modules/underdogmediaBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adloader from '../../../src/adloader'; - -import { - expect -} from 'chai'; - -describe('underdogmedia adapter test', () => { - let adapter; - let server; - - // The third bid here is an invalid site id and should return a 'no-bid'. - - var bidderRequest = { - bidderCode: 'underdogmedia', - bids: [{ - bidder: 'underdogmedia', - adUnitCode: 'foo', - sizes: [ - [728, 90] - ], - params: { - siteId: '10272' - } - }, - { - bidder: 'underdogmedia', - adUnitCode: 'bar', - sizes: [ - [300, 250] - ], - params: { - siteId: '10272', - subId: 'TEST_SUBID' - } - }, - { - bidder: 'underdogmedia', - adUnitCode: 'nothing', - sizes: [160, 600], - params: { - siteId: '31337' - } - } - ] - }; - var response = { - 'mids': [{ - 'width': 728, - 'notification_url': '//udmserve.net/notification_url', - 'height': 90, - 'cpm': 2.5, - 'ad_code_html': 'Ad HTML for site ID 10272 size 728x90' - }, - { - 'width': 300, - 'notification_url': '//udmserve.net/notification_url', - 'height': 250, - 'cpm': 2.0, - 'ad_code_html': 'Ad HTML for site ID 10272 size 300x250' - } - ] - }; +import { expect } from 'chai'; +import { spec } from 'modules/underdogmediaBidAdapter'; + +describe('UnderdogMedia adapter', () => { + let bidRequests; beforeEach(() => { - adapter = new Adapter(); + bidRequests = [ + { + bidder: 'underdogmedia', + params: { + siteId: 12143 + }, + adUnitCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; }); - afterEach(() => {}); + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + let validBid = { + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + sizes: [[300, 250], [300, 600]] + }; + const isValid = spec.isBidRequestValid(validBid); - describe('adding bids to the manager', () => { - let firstBid; - let secondBid; - let thirdBid; + expect(isValid).to.equal(true); + }); - beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - sinon.stub(adloader, 'loadScript'); + it('should reject invalid bid missing sizes', () => { + let invalidBid = { + bidder: 'underdogmedia', + params: { + siteId: '12143', + } + }; + const isValid = spec.isBidRequestValid(invalidBid); - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleUnderdogMediaCB(JSON.parse(JSON.stringify(response))); - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - thirdBid = bidManager.addBidResponse.thirdCall.args[1]; - }); + expect(isValid).to.equal(false); + }); - afterEach(() => { - bidManager.addBidResponse.restore(); - adloader.loadScript.restore(); - }); + it('should reject invalid bid missing siteId', () => { + let invalidBid = { + bidder: 'underdogmedia', + params: {}, + sizes: [[300, 250], [300, 600]] + }; + const isValid = spec.isBidRequestValid(invalidBid); - it('will add a bid object for each bid', () => { - sinon.assert.calledThrice(bidManager.addBidResponse); - }); + expect(isValid).to.equal(false); + }); - it('will add the ad html to the bid object', () => { - expect(firstBid).to.have.property('ad').includes('Ad HTML for site ID 10272 size 728x90'); - expect(secondBid).to.have.property('ad').includes('Ad HTML for site ID 10272 size 300x250').and.includes('TEST_SUBID'); - expect(thirdBid).to.not.have.property('ad'); - }); + it('request data should contain sid', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250]], + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + requestId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + } + ]; + const request = spec.buildRequests(bidRequests); - it('will have the right size attached', () => { - expect(firstBid).to.have.property('width', 728); - expect(firstBid).to.have.property('height', 90); - expect(secondBid).to.have.property('width', 300); - expect(secondBid).to.have.property('height', 250); - }); + expect(request.data).to.have.string('sid=12143'); + }); + + it('request data should contain sizes', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250], [728, 90]], + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + requestId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + } + ]; + const request = spec.buildRequests(bidRequests); - it('will add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 2.5); - expect(secondBid).to.have.property('cpm', 2.0); - expect(thirdBid).to.not.have.property('cpm'); + expect(request.data).to.have.string('sizes=300x250,728x90'); + }); }); - it('will add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'underdogmedia'); - expect(secondBid).to.have.property('bidderCode', 'underdogmedia'); - expect(thirdBid).to.have.property('bidderCode', 'underdogmedia'); + describe('bid responses', () => { + it('should return complete bid response', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }, + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '250', + mid: '32633', + notification_url: 'notification_url', + tid: '2', + width: '300' + }, + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(2); + + expect(bids[0].bidderCode).to.equal('underdogmedia'); + expect(bids[0].cpm).to.equal(2.5); + expect(bids[0].width).to.equal('160'); + expect(bids[0].height).to.equal('600'); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].creativeId).to.equal('32634'); + expect(bids[0].currency).to.equal('USD'); + }); + + it('should return empty bid response if mids empty', () => { + let serverResponse = { + body: { + mids: [] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '123', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on 0 cpm', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 0, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response if no ad in response', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: '', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('ad html string should contain the notification urls', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_cod_html', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids[0].ad).to.have.string('notification_url'); + expect(bids[0].ad).to.have.string(';style=adapter'); + }); }); }); }); From 4e2c2a97c079b888f4968a1dddf4036e5ed90554 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Wed, 8 Nov 2017 15:30:33 -0500 Subject: [PATCH 56/62] Unit test fix (#1812) --- test/spec/modules/vertamediaBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/vertamediaBidAdapter_spec.js b/test/spec/modules/vertamediaBidAdapter_spec.js index 4f04ea8a615..d505472dea1 100644 --- a/test/spec/modules/vertamediaBidAdapter_spec.js +++ b/test/spec/modules/vertamediaBidAdapter_spec.js @@ -88,7 +88,6 @@ describe('vertamediaBidAdapter', () => { vastUrl: 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', descriptionUrl: '44F2AEB9BFC881B3', requestId: '2e41f65424c87c', - bidderCode: 'bidderCode', creativeId: 342516, mediaType: 'video', netRevenue: true, From 1895cb3ce6045199f5150f6b9820cdf4bc1c2a0d Mon Sep 17 00:00:00 2001 From: Aparna Rao-Hegde Date: Thu, 9 Nov 2017 10:19:43 -0500 Subject: [PATCH 57/62] Adding 33Across adapter (#1805) * Adding 33across adapter * Updated per code review from Prebid. See https://github.com/prebid/Prebid.js/pull/1805#pullrequestreview-75218582 --- modules/33acrossBidAdapter.js | 140 ++++++ modules/33acrossBidAdapter.md | 95 ++++ test/spec/modules/33acrossBidAdapter_spec.js | 443 +++++++++++++++++++ 3 files changed, 678 insertions(+) create mode 100644 modules/33acrossBidAdapter.js create mode 100644 modules/33acrossBidAdapter.md create mode 100644 test/spec/modules/33acrossBidAdapter_spec.js diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js new file mode 100644 index 00000000000..b496a66d081 --- /dev/null +++ b/modules/33acrossBidAdapter.js @@ -0,0 +1,140 @@ +const { registerBidder } = require('../src/adapters/bidderFactory'); +const utils = require('../src/utils'); + +const BIDDER_CODE = '33across'; +const END_POINT = 'https://ssc.33across.com/api/v1/hb'; +const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch'; + +// All this assumes that only one bid is ever returned by ttx +function _createBidResponse(response) { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].ext.rp.advid, + currency: response.cur, + netRevenue: true + } +} + +// infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request +function _createServerRequest(bidRequest) { + const ttxRequest = {}; + const params = bidRequest.params; + + ttxRequest.imp = []; + ttxRequest.imp[0] = { + banner: { + format: bidRequest.sizes.map(_getFormatSize) + }, + ext: { + ttx: { + prod: params.productId + } + } + } + + // Allowing site to be a test configuration object or just the id (former required for testing, + // latter when used by publishers) + ttxRequest.site = params.site || { id: params.siteId }; + + // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and + // therefore in ad targetting process + ttxRequest.id = bidRequest.bidId; + + const options = { + contentType: 'application/json', + withCredentials: false + }; + + if (bidRequest.params.customHeaders) { + options.customHeaders = bidRequest.params.customHeaders; + } + + return { + 'method': 'POST', + 'url': bidRequest.params.url || END_POINT, + 'data': JSON.stringify(ttxRequest), + 'options': options + } +} + +// Sync object will always be of type iframe for ttx +function _createSync(bid) { + const syncUrl = bid.params.syncUrl || SYNC_ENDPOINT; + + return { + type: 'iframe', + url: `${syncUrl}&id=${bid.params.siteId || bid.params.site.id}` + } +} + +function _getFormatSize(sizeArr) { + return { + w: sizeArr[0], + h: sizeArr[1], + ext: {} + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if ((typeof bid.params.site === 'undefined' || typeof bid.params.site.id === 'undefined') && + (typeof bid.params.siteId === 'undefined')) { + return false; + } + + if (typeof bid.params.productId === 'undefined') { + return false; + } + + return true; +} + +// NOTE: At this point, 33exchange only accepts request for a single impression +function buildRequests(bidRequests) { + return bidRequests.map(_createServerRequest); +} + +// NOTE: At this point, the response from 33exchange will only ever contain one bid i.e. the highest bid +function interpretResponse(serverResponse) { + const bidResponses = []; + + // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx) + if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) { + bidResponses.push(_createBidResponse(serverResponse.body)); + } + + return bidResponses; +} + +// Register one sync per bid since each ad unit may potenitally be linked to a uniqe guid +function getUserSyncs(syncOptions) { + let syncs = []; + const ttxBidRequests = utils.getBidderRequestAllAdUnits(BIDDER_CODE).bids; + + if (syncOptions.iframeEnabled) { + syncs = ttxBidRequests.map(_createSync); + } + + return syncs; +} + +const spec = { + code: BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} + +registerBidder(spec); + +module.exports = spec; diff --git a/modules/33acrossBidAdapter.md b/modules/33acrossBidAdapter.md new file mode 100644 index 00000000000..c4eb319c157 --- /dev/null +++ b/modules/33acrossBidAdapter.md @@ -0,0 +1,95 @@ +# Overview + +``` +Module Name: 33Across Bid Adapter +Module Type: Bidder Adapter +Maintainer: aparna.hegde@33across.com +``` + +# Description + +Connects to 33Across's exchange for bids. + +33Across bid adapter supports only Banner at present and follows MRA + +# Sample Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', + sizes: [ + [300, 250], + [728, 90] + ], + bids: [{ + bidder: '33across', + params: { + siteId: 'pub1234', + productId: 'infeed' + } + }] +} +``` + +# Ad Unit and Setup: For Testing +In order to receive bids please map localhost to (any) test domain. + +``` +<--! Prebid Config section > + + `, ``); + iframeDoc.write(content); iframeDoc.close(); } - function _createRequestContent() { - var content = 'inDapIF=true;'; - content += ''; - content += ''; - content += '' + - 'window.pm_pub_id = "%%PM_PUB_ID%%";' + - 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];' + - 'window.kaddctr = "%%PM_ADDCTR%%";' + - 'window.kadgender = "%%PM_GENDER%%";' + - 'window.kadage = "%%PM_AGE%%";' + - 'window.pm_async_callback_fn = "window.parent.$$PREBID_GLOBAL$$.handlePubmaticCallback";'; - - content += ''; - - var map = {}; - map.PM_PUB_ID = _pm_pub_id; - map.PM_ADDCTR = _pm_pub_kvs; - map.PM_GENDER = _pm_pub_gender; - map.PM_AGE = _pm_pub_age; - map.PM_OPTIMIZE_ADSLOTS = _pm_optimize_adslots.map(function (adSlot) { - return "'" + adSlot + "'"; - }).join(','); - - content += ''; - content += ''; - content += ''; - content += ''; - content = utils.replaceTokenInString(content, map, '%%'); - - return content; + function _generateLegacyCall(conf, slots) { + var request_url = 'gads.pubmatic.com/AdServer/AdCallAggregator'; + return _protocol + request_url + '?' + utils.parseQueryStringParameters(conf) + 'adslots=' + encodeURIComponent('[' + slots.join(',') + ']'); } - $$PREBID_GLOBAL$$.handlePubmaticCallback = function () { - let bidDetailsMap = {}; - let progKeyValueMap = {}; - try { - bidDetailsMap = iframe.contentWindow.bidDetailsMap; - progKeyValueMap = iframe.contentWindow.progKeyValueMap; - } catch (e) { - utils.logError(e, 'Error parsing Pubmatic response'); + function _initUserSync(pubId) { + // istanbul ignore else + if (!usersync) { + var iframe = utils.createInvisibleIframe(); + iframe.src = _protocol + 'ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=' + pubId; + utils.insertElement(iframe, document); + usersync = true; + } + } + + function _callBids(params) { + var conf = _initConf(); + var slots = []; + + conf.pubId = 0; + bids = params.bids || []; + + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + conf.pubId = conf.pubId || bid.params.publisherId; + conf = _handleCustomParams(bid.params, conf); + bid.params.adSlot = _cleanSlot(bid.params.adSlot); + bid.params.adSlot.length && slots.push(bid.params.adSlot); + } + + // istanbul ignore else + if (conf.pubId && slots.length > 0) { + _legacyExecution(conf, slots); } + _initUserSync(conf.pubId); + } + + $$PREBID_GLOBAL$$.handlePubmaticCallback = function(bidDetailsMap, progKeyValueMap) { var i; var adUnit; var adUnitInfo; var bid; - var bidResponseMap = bidDetailsMap || {}; - var bidInfoMap = progKeyValueMap || {}; - var dimensions; + var bidResponseMap = bidDetailsMap; + var bidInfoMap = progKeyValueMap; + + if (!bidResponseMap || !bidInfoMap) { + return; + } for (i = 0; i < bids.length; i++) { var adResponse; bid = bids[i].params; - adUnit = bidResponseMap[bid.adSlot] || {}; - // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= - // if using DFP GPT, the params string comes in the format: // "bidstatus;1;bid;5.0000;bidid;hb_test@468x60;wdeal;" // the code below detects and handles this. + // istanbul ignore else if (bidInfoMap[bid.adSlot] && bidInfoMap[bid.adSlot].indexOf('=') === -1) { bidInfoMap[bid.adSlot] = bidInfoMap[bid.adSlot].replace(/([a-z]+);(.[^;]*)/ig, '$1=$2'); } - adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function (result, pair) { + adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function(result, pair) { var parts = pair.split('='); result[parts[0]] = parts[1]; return result; }, {}); if (adUnitInfo.bidstatus === '1') { - dimensions = adUnitInfo.bidid.split('@')[1].split('x'); adResponse = bidfactory.createBid(1); adResponse.bidderCode = 'pubmatic'; adResponse.adSlot = bid.adSlot; adResponse.cpm = Number(adUnitInfo.bid); adResponse.ad = unescape(adUnit.creative_tag); adResponse.ad += utils.createTrackPixelIframeHtml(decodeURIComponent(adUnit.tracking_url)); - adResponse.width = dimensions[0]; - adResponse.height = dimensions[1]; + adResponse.width = adUnit.width; + adResponse.height = adUnit.height; adResponse.dealId = adUnitInfo.wdeal; + adResponse.dealChannel = dealChannelValues[adUnit.deal_channel] || null; bidmanager.addBidResponse(bids[i].placementCode, adResponse); } else { @@ -147,7 +226,7 @@ function PubmaticAdapter() { return { callBids: _callBids }; -} +}; adaptermanager.registerBidAdapter(new PubmaticAdapter(), 'pubmatic'); diff --git a/src/utils.js b/src/utils.js index f14a9bbd46c..6dc30a184d2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -738,6 +738,19 @@ export function deepAccess(obj, path) { return obj; } +/** + * Returns content for a friendly iframe to execute a URL in script tag + * @param {url} URL to be executed in a script tag in a friendly iframe + * and are macros left to be replaced if required + */ +export function createContentToExecuteExtScriptInFriendlyFrame(url) { + if (!url) { + return ''; + } + + return ``; +} + /** * Build an object consisting of only defined parameters to avoid creating an * object with defined keys and undefined values. diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js new file mode 100644 index 00000000000..c7b8cd5cd8e --- /dev/null +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -0,0 +1,276 @@ +import { + expect +} from 'chai'; +import * as utils from 'src/utils'; +import PubMaticAdapter from 'modules/pubmaticBidAdapter'; +import bidmanager from 'src/bidmanager'; +import constants from 'src/constants.json'; + +let getDefaultBidRequest = () => { + return { + bidderCode: 'pubmatic', + requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + bidderRequestId: '7101db09af0db2', + start: new Date().getTime(), + bids: [{ + bidder: 'pubmatic', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + placementCode: 'DIV_1', + params: { + placement: 1234567, + network: '9599.1' + } + }] + }; +}; + +describe('PubMaticAdapter', () => { + let adapter; + + function createBidderRequest({ + bids, + params + } = {}) { + var bidderRequest = getDefaultBidRequest(); + if (bids && Array.isArray(bids)) { + bidderRequest.bids = bids; + } + if (params) { + bidderRequest.bids.forEach(bid => bid.params = params); + } + return bidderRequest; + } + + beforeEach(() => adapter = new PubMaticAdapter()); + + describe('callBids()', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + + describe('user syncup', () => { + beforeEach(() => { + sinon.stub(utils, 'insertElement'); + }); + + afterEach(() => { + utils.insertElement.restore(); + }); + + it('usersync is initiated', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9999, + adSlot: 'abcd@728x90', + age: '20' + } + })); + utils.insertElement.calledOnce.should.be.true; + expect(utils.insertElement.getCall(0).args[0].src).to.equal('http://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=9999'); + }); + }); + + describe('bid request', () => { + beforeEach(() => { + sinon.stub(utils, 'createContentToExecuteExtScriptInFriendlyFrame', function() { + return ''; + }); + }); + + afterEach(() => { + utils.createContentToExecuteExtScriptInFriendlyFrame.restore(); + }); + + it('requires parameters to be made', () => { + adapter.callBids({}); + utils.createContentToExecuteExtScriptInFriendlyFrame.calledOnce.should.be.false; + }); + + it('for publisherId 9990 call is made to gads.pubmatic.com', () => { + var bidRequest = createBidderRequest({ + params: { + publisherId: 9990, + adSlot: ' abcd@728x90', + age: '20', + wiid: 'abcdefghijk', + profId: '1234', + verId: '12', + pmzoneid: 'abcd123, efg345', + dctr: 'key=1234,5678' + } + }); + adapter.callBids(bidRequest); + var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0]; + expect(bidRequest.bids[0].params.adSlot).to.equal('abcd@728x90'); + expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?'); + expect(callURL).to.contain('SAVersion=1100'); + expect(callURL).to.contain('wp=PreBid'); + expect(callURL).to.contain('js=1'); + expect(callURL).to.contain('screenResolution='); + expect(callURL).to.contain('wv=' + constants.REPO_AND_VERSION); + expect(callURL).to.contain('ranreq='); + expect(callURL).to.contain('inIframe='); + expect(callURL).to.contain('pageURL='); + expect(callURL).to.contain('refurl='); + expect(callURL).to.contain('kltstamp='); + expect(callURL).to.contain('timezone='); + expect(callURL).to.contain('age=20'); + expect(callURL).to.contain('adslots=%5Babcd%40728x90%5D'); + expect(callURL).to.contain('kadpageurl='); + expect(callURL).to.contain('wiid=abcdefghijk'); + expect(callURL).to.contain('profId=1234'); + expect(callURL).to.contain('verId=12'); + expect(callURL).to.contain('pmZoneId=abcd123%2C%20efg345'); + expect(callURL).to.contain('dctr=key%3D1234%2C5678'); + }); + + it('for publisherId 9990 call is made to gads.pubmatic.com, age passed as int not being passed ahead', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9990, + adSlot: 'abcd@728x90', + age: 20, + wiid: 'abcdefghijk', + profId: '1234', + verId: '12', + pmzoneid: {}, + dctr: 1234 + } + })); + var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0]; + expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?'); + expect(callURL).to.not.contain('age=20'); + expect(callURL).to.not.contain('dctr=1234'); + }); + + it('for publisherId 9990 call is made to gads.pubmatic.com, invalid data for pmzoneid', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9990, + adSlot: 'abcd@728x90', + age: '20', + wiid: 'abcdefghijk', + profId: '1234', + verId: '12', + pmzoneid: {}, + dctr: 1234 + } + })); + var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0]; + expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?'); + expect(callURL).to.not.contain('pmZoneId='); + }); + }); + + describe('#handlePubmaticCallback: ', () => { + beforeEach(() => { + sinon.stub(utils, 'createContentToExecuteExtScriptInFriendlyFrame', function() { + return ''; + }); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + utils.createContentToExecuteExtScriptInFriendlyFrame.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('exists and is a function', () => { + expect($$PREBID_GLOBAL$$.handlePubmaticCallback).to.exist.and.to.be.a('function'); + }); + + it('empty response, arguments not passed', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9999, + adSlot: 'abcd@728x90', + age: '20' + } + })); + $$PREBID_GLOBAL$$.handlePubmaticCallback(); + expect(bidmanager.addBidResponse.callCount).to.equal(0); + }); + + it('empty response', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9999, + adSlot: 'abcd@728x90', + age: '20' + } + })); + $$PREBID_GLOBAL$$.handlePubmaticCallback({}, {}); + sinon.assert.called(bidmanager.addBidResponse); + expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1'); + var theBid = bidmanager.addBidResponse.firstCall.args[1]; + expect(theBid.bidderCode).to.equal('pubmatic'); + expect(theBid.getStatusCode()).to.equal(2); + }); + + it('not empty response', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9999, + adSlot: 'abcd@728x90:0', + age: '20' + } + })); + $$PREBID_GLOBAL$$.handlePubmaticCallback({ + 'abcd@728x90:0': { + 'ecpm': 10, + 'creative_tag': 'hello', + 'tracking_url': 'http%3a%2f%2fhaso.pubmatic.com%2fads%2f9999%2fGRPBID%2f2.gif%3ftrackid%3d12345', + 'width': 728, + 'height': 90, + 'deal_channel': 5 + } + }, { + 'abcd@728x90:0': 'bidstatus;1;bid;10.0000;bidid;abcd@728x90:0;wdeal;PMERW36842' + }); + sinon.assert.called(bidmanager.addBidResponse); + expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1'); + var theBid = bidmanager.addBidResponse.firstCall.args[1]; + expect(theBid.bidderCode).to.equal('pubmatic'); + expect(theBid.adSlot).to.equal('abcd@728x90:0'); + expect(theBid.cpm).to.equal(10); + expect(theBid.width).to.equal(728); + expect(theBid.height).to.equal(90); + expect(theBid.dealId).to.equal('PMERW36842'); + expect(theBid.dealChannel).to.equal('PREF'); + }); + + it('not empty response, without dealChannel', () => { + adapter.callBids(createBidderRequest({ + params: { + publisherId: 9999, + adSlot: 'abcd@728x90', + age: '20' + } + })); + $$PREBID_GLOBAL$$.handlePubmaticCallback({ + 'abcd@728x90': { + 'ecpm': 10, + 'creative_tag': 'hello', + 'tracking_url': 'http%3a%2f%2fhaso.pubmatic.com%2fads%2f9999%2fGRPBID%2f2.gif%3ftrackid%3d12345', + 'width': 728, + 'height': 90 + } + }, { + 'abcd@728x90': 'bidstatus;1;bid;10.0000;bidid;abcd@728x90:0;wdeal;PMERW36842' + }); + sinon.assert.called(bidmanager.addBidResponse); + expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1'); + var theBid = bidmanager.addBidResponse.firstCall.args[1]; + expect(theBid.bidderCode).to.equal('pubmatic'); + expect(theBid.adSlot).to.equal('abcd@728x90'); + expect(theBid.cpm).to.equal(10); + expect(theBid.width).to.equal(728); + expect(theBid.height).to.equal(90); + expect(theBid.dealId).to.equal('PMERW36842'); + expect(theBid.dealChannel).to.equal(null); + }); + }); + }); +}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index ad2645b2351..a08abaee847 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -658,6 +658,20 @@ describe('Utils', function () { }); }); + describe('createContentToExecuteExtScriptInFriendlyFrame', function () { + it('should return empty string if url is not passed', function () { + var output = utils.createContentToExecuteExtScriptInFriendlyFrame(); + assert.equal(output, ''); + }); + + it('should have URL in returned value if url is passed', function () { + var url = 'https://abcd.com/service?a=1&b=2&c=3'; + var output = utils.createContentToExecuteExtScriptInFriendlyFrame(url); + var expected = ``; + assert.equal(output, expected); + }); + }); + describe('getDefinedParams', () => { it('builds an object consisting of defined params', () => { const adUnit = { From cb2cd7740a65fa9809aebef567a8d40576ab1c58 Mon Sep 17 00:00:00 2001 From: hdjvieira Date: Fri, 10 Nov 2017 18:03:07 +0000 Subject: [PATCH 61/62] Update Pollux Adapter to v1.0 (#1694) * Added PolluxNetwork Bid Adapter Added module, test spec and integration example for Pollux Network Bid Adapter * Update Pollux domain Update Pollux default domain on prebid adapter * Export getParameterByName method On Utils.js make getParameterByName method public * Executed changes requested by @jaiminpanchal27 on 2017-08-01 Moved zone_728x90.html to integrationExamples/gpt/pollux_zone_728x90.html; Added bidRequest as second parameter to bidfactory.createBid() on Pollux Bid Adapter; Added more test cases to increase test coverage (at least 85%); Review Ref: - https://github.com/prebid/Prebid.js/pull/1431#pullrequestreview-53608436 * Fixed Eslint errors on commit f745fe1 * Executed changes requested on PR#1431 review #54993573 - Removed $$PREBID_GLOBAL$$ public vars in unit test; - Moved stubs creation and its restoration to beforeEach and afterEach hooks in unit test; - Exposed polluxHandler method on polluxBidAdapter. * Remove redundant export This line was added in #1409, removing this then I'll merge * Update Pollux Adapter to v1.0 * Changes requested on Pollux Adapter pull request #1694 review #74933409 * Changes requested on Pollux Adapter pull request #1694 review #75505070 Rmoved parameter bidderCode from bid responses * Fixed breaking changes to serverResponse in interpretResponse method Parameter serverResponse of method interpretResponse in bid adapter changed from array of bids to an object, where bids are now nested within its parameter body. Plus a refactor of var declaration and log messages. * Fix lint errors on push for commit cc653a --- integrationExamples/gpt/pbjs_example_gpt.html | 12 + ...x_zone_728x90.html => pollux_example.html} | 100 +++-- modules/polluxBidAdapter.js | 199 +++++----- modules/polluxBidAdapter.md | 33 ++ test/spec/modules/polluxBidAdapter_spec.js | 351 ++++++++++-------- 5 files changed, 413 insertions(+), 282 deletions(-) rename integrationExamples/gpt/{pollux_zone_728x90.html => pollux_example.html} (51%) create mode 100644 modules/polluxBidAdapter.md diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 07e5bb8236f..1493634c1c7 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -269,6 +269,12 @@ placement_id: 0 } }, + { + bidder: 'pollux', + params: { + zone: '1806' // REQUIRED Zone Id (1806 is a test zone) + } + }, { bidder: 'adkernelAdn', params: { @@ -395,6 +401,12 @@ params: { placement_id: 0 } + }, + { + bidder: 'pollux', + params: { + zone: '276' // REQUIRED Zone Id (276 is a test zone) + } } ] } diff --git a/integrationExamples/gpt/pollux_zone_728x90.html b/integrationExamples/gpt/pollux_example.html similarity index 51% rename from integrationExamples/gpt/pollux_zone_728x90.html rename to integrationExamples/gpt/pollux_example.html index ecede9b5db2..56eedbf2a9c 100644 --- a/integrationExamples/gpt/pollux_zone_728x90.html +++ b/integrationExamples/gpt/pollux_example.html @@ -7,31 +7,40 @@ var PREBID_TIMEOUT = 3000; var adUnits = [{ - code: 'div-gpt-ad-1460505661639-0', - sizes: [[728, 90]], - bids: [ - { - bidder: 'pollux', - params: { - zone: '276' - } - }, - { - bidder: 'pollux', - params: { - zone: '1806' - } - } - ] - }]; - + code: 'div-gpt-ad-1460505661639-0', + sizes: [[728, 90], [300, 250]], + bids: [{ + bidder: 'pollux', + params: { + zone: '1806,276' + } + }, { + bidder: 'pollux', + params: { + zone: '276' + } + } + ] + }, + { + code: 'div-gpt-ad-1460505661631-0', + sizes: [[300, 250]], + bids: [{ + bidder: 'pollux', + params: { + zone: '1806,276,855' + } + } + ] + } + ]; var pbjs = pbjs || {}; pbjs.que = pbjs.que || []; - + @@ -79,7 +97,8 @@ + +
+
+ +
+ +
\ No newline at end of file diff --git a/modules/polluxBidAdapter.js b/modules/polluxBidAdapter.js index 54c2122ec36..463de07341c 100644 --- a/modules/polluxBidAdapter.js +++ b/modules/polluxBidAdapter.js @@ -1,97 +1,120 @@ -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import adloader from 'src/adloader'; -import adaptermanager from 'src/adaptermanager'; -import { STATUS } from 'src/constants'; +import { registerBidder } from 'src/adapters/bidderFactory'; -// Prebid adapter for Pollux header bidding client -function PolluxBidAdapter() { - function _callBids(params) { - var bidderUrl = (window.location.protocol) + '//adn.plxnt.com/prebid'; - var bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - var request_obj = {}; - var bid = bids[i]; - // check params - if (bid.params.zone) { - var domain = utils.getParameterByName('domain'); - var tracker2 = utils.getParameterByName('tracker2'); - if (domain) { - request_obj.domain = domain; - } else { - request_obj.domain = window.location.host; - } - if (tracker2) { - request_obj.tracker2 = tracker2; - } - request_obj.zone = bid.params.zone; - } else { - utils.logError('required param "zone" is missing', 'polluxHandler'); - continue; - } - var parsedSizes = utils.parseSizesInput(bid.sizes); - var parsedSizesLength = parsedSizes.length; - if (parsedSizesLength > 0) { - // first value should be "size" - request_obj.size = parsedSizes[0]; - if (parsedSizesLength > 1) { - // any subsequent values should be "promo_sizes" - var promo_sizes = []; - for (var j = 1; j < parsedSizesLength; j++) { - promo_sizes.push(parsedSizes[j]); - } - request_obj.promo_sizes = promo_sizes.join(','); - } - } - // detect urls - request_obj.callback_id = bid.bidId; - // set a different url bidder - if (bid.bidderUrl) { - bidderUrl = bid.bidderUrl; +const BIDDER_CODE = 'pollux'; +const PLX_ENDPOINT_URL = '//adn.plxnt.com/prebid/v1'; +const PLX_CURRENCY = 'EUR'; +const PLX_TTL = 3600; +const PLX_NETREVENUE = true; + +export const spec = { + code: BIDDER_CODE, + aliases: ['plx'], + /** + * 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.hasOwnProperty('params') || !bid.params.hasOwnProperty('zone')) { + utils.logError('required param "zone" is missing for == ' + BIDDER_CODE + ' =='); + return false; + } + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + if (!Array.isArray(validBidRequests) || !validBidRequests.length) { + return []; + } + const payload = []; + let custom_url = null; + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const request = { + bidId: bid.bidId, + zones: bid.params.zone, + sizes: bid.sizes + }; + if (bid.bidderUrl && !custom_url) { + custom_url = bid.bidderUrl; } - var prebidUrl = bidderUrl + '?' + utils.parseQueryStringParameters(request_obj); - utils.logMessage('Pollux request built: ' + prebidUrl); - adloader.loadScript(prebidUrl, null, true); + payload.push(request); } - } - - // expose the callback to global object - function _polluxHandler (response) { - // pollux handler - var bidObject = {}; - var callback_id = response.callback_id; - var placementCode = ''; - var bidObj = utils.getBidRequest(callback_id); - if (bidObj) { - placementCode = bidObj.placementCode; + const payloadString = JSON.stringify(payload); + // build url parameters + const domain = utils.getParameterByName('domain'); + const tracker2 = utils.getParameterByName('tracker2'); + const url_params = {}; + if (domain) { + url_params.domain = domain; + } else { + url_params.domain = utils.getTopWindowUrl(); + } + if (tracker2) { + url_params.tracker2 = tracker2; + } + // build url + let bidder_url = custom_url || PLX_ENDPOINT_URL; + if (url_params) { + bidder_url = bidder_url + '?' + utils.parseQueryStringParameters(url_params); + } + utils.logMessage('== ' + BIDDER_CODE + ' == request built: ' + bidder_url); + return { + method: 'POST', + url: bidder_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 = []; + if (!serverResponse || (typeof serverResponse === 'object' && !serverResponse.hasOwnProperty('body'))) { + utils.logMessage('No prebid response from == ' + BIDDER_CODE + ' == for bid requests:'); + utils.logMessage(bidRequest); + return bidResponses; } - if (bidObj && response.cpm > 0 && !!response.ad) { - bidObject = bidfactory.createBid(STATUS.GOOD, bidObj); - bidObject.bidderCode = bidObj.bidder; - bidObject.mediaType = response.mediaType; - bidObject.cpm = parseFloat(response.cpm); - if (response.ad_type === 'url') { - bidObject.adUrl = response.ad; + serverResponse = serverResponse.body; + if (!Array.isArray(serverResponse) || !serverResponse.length) { + utils.logMessage('No prebid response from == ' + BIDDER_CODE + ' == for bid requests:'); + utils.logMessage(bidRequest); + return bidResponses; + } + // loop through serverResponses + for (let b in serverResponse) { + let bid = serverResponse[b]; + const bidResponse = { + requestId: bid.bidId, // not request id, it's bid's id + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width), + height: parseInt(bid.height), + ttl: PLX_TTL, + creativeId: bid.creativeId, + netRevenue: PLX_NETREVENUE, + currency: PLX_CURRENCY + }; + if (bid.ad_type === 'url') { + bidResponse.adUrl = bid.ad; } else { - bidObject.ad = response.ad; + bidResponse.ad = bid.ad; } - bidObject.width = response.width; - bidObject.height = response.height; - } else { - bidObject = bidfactory.createBid(STATUS.NO_BID, bidObj); - bidObject.bidderCode = 'pollux'; - utils.logMessage('No prebid response from polluxHandler for placement code ' + placementCode); + if (bid.referrer) { + bidResponse.referrer = bid.referrer; + } + bidResponses.push(bidResponse); } - bidmanager.addBidResponse(placementCode, bidObject); - }; - $$PREBID_GLOBAL$$.polluxHandler = _polluxHandler; - // Export the `callBids` function, so that Prebid.js can execute - // this function when the page asks to send out bid requests. - return { - callBids: _callBids, - polluxHandler: _polluxHandler - }; + return bidResponses; + } }; -adaptermanager.registerBidAdapter(new PolluxBidAdapter(), 'pollux'); -module.exports = PolluxBidAdapter; +registerBidder(spec); diff --git a/modules/polluxBidAdapter.md b/modules/polluxBidAdapter.md new file mode 100644 index 00000000000..79bf84e79b9 --- /dev/null +++ b/modules/polluxBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name**: Pollux Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@polluxnetwork.com + +# Description + +Module that connects to Pollux Network LLC demand source to fetch bids. +All bids will present CPM in EUR (Euro). + +# Test Parameters +``` + var adUnits = [{ + code: '34f724kh32', + sizes: [[300, 250]], // a single size + bids: [{ + bidder: 'pollux', + params: { + zone: '1806' // a single zone + } + }] + },{ + code: '34f789r783', + sizes: [[300, 250], [728, 90]], // multiple sizes + bids: [{ + bidder: 'pollux', + params: { + zone: '1806,276' // multiple zones, max 5 + } + }] + }]; +``` diff --git a/test/spec/modules/polluxBidAdapter_spec.js b/test/spec/modules/polluxBidAdapter_spec.js index 1bcfe28124d..ea550fecd71 100644 --- a/test/spec/modules/polluxBidAdapter_spec.js +++ b/test/spec/modules/polluxBidAdapter_spec.js @@ -1,172 +1,207 @@ -describe('Pollux Bid Adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var Adapter = require('modules/polluxBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - var utils = require('src/utils'); - - var stubLoadScript; - var stubAddBidResponse; - var polluxAdapter; - - // mock golbal _bidsRequested var - var bidsRequested = []; - utils.getBidRequest = function (id) { - return bidsRequested.map(bidSet => bidSet.bids.find(bid => bid.bidId === id)).find(bid => bid); - }; - - beforeEach(function () { - polluxAdapter = new Adapter(); - bidsRequested = []; - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); +import {expect} from 'chai'; +import {spec} from 'modules/polluxBidAdapter'; +import {utils} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('POLLUX Bid Adapter tests', function () { + // ad units setup + const setup_single_bid = [{ + placementCode: 'div-gpt-ad-1460505661587-0', + bidId: '789s6354sfg856', + bidderUrl: '//adn.polluxnetwork.com/prebid/v1', + sizes: [[728, 90], [300, 250]], + params: {zone: '1806,276'} + }]; + const setup_multi_bid = [{ + placementCode: 'div-gpt-ad-1460505661639-0', + bidId: '21fe992ca48d55', + sizes: [[300, 250]], + params: {zone: '1806'} + }, { + placementCode: 'div-gpt-ad-1460505661812-0', + bidId: '23kljh54390534', + sizes: [[728, 90]], + params: {zone: '276'} + }]; + + it('TEST: verify buildRequests no valid bid requests', () => { + let request = spec.buildRequests(false); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests([]); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests({}); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests(null); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); }); - afterEach(function () { - stubLoadScript.restore(); - stubAddBidResponse.restore(); + it('TEST: verify buildRequests single bid', () => { + const request = spec.buildRequests(setup_single_bid); + expect(request.method).to.equal('POST'); + const requested_bids = JSON.parse(request.data); + // bids request + expect(requested_bids).to.not.equal(null); + expect(requested_bids).to.have.lengthOf(1); + // bid objects + expect(requested_bids[0]).to.not.equal(null); + expect(requested_bids[0]).to.have.property('bidId'); + expect(requested_bids[0]).to.have.property('sizes'); + expect(requested_bids[0]).to.have.property('zones'); + // bid 0 + expect(requested_bids[0].bidId).to.equal('789s6354sfg856'); + expect(requested_bids[0].sizes).to.not.equal(null); + expect(requested_bids[0].sizes).to.have.lengthOf(2); + expect(requested_bids[0].sizes[0][0]).to.equal(728); + expect(requested_bids[0].sizes[0][1]).to.equal(90); + expect(requested_bids[0].sizes[1][0]).to.equal(300); + expect(requested_bids[0].sizes[1][1]).to.equal(250); + expect(requested_bids[0].zones).to.equal('1806,276'); }); - describe('creation of bid url', function () { - it('bid request for single placement', function () { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - bidId: '21fe992ca48d55', - bidder: 'pollux', - sizes: [[300, 250]], - params: { zone: '1806' } - }] - }; - - polluxAdapter.callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrlQueryString).to.have.property('zone').and.to.equal('1806'); - expect(parsedBidUrlQueryString).to.have.property('domain').and.to.have.length.above(1); - }); + it('TEST: verify buildRequests multi bid', () => { + const request = spec.buildRequests(setup_multi_bid); + expect(request.method).to.equal('POST'); + const requested_bids = JSON.parse(request.data); + // bids request + expect(requested_bids).to.not.equal(null); + expect(requested_bids).to.have.lengthOf(2); + // bid objects + expect(requested_bids[0]).to.not.equal(null); + expect(requested_bids[0]).to.have.property('bidId'); + expect(requested_bids[0]).to.have.property('sizes'); + expect(requested_bids[0]).to.have.property('zones'); + expect(requested_bids[1]).to.not.equal(null); + expect(requested_bids[1]).to.have.property('bidId'); + expect(requested_bids[1]).to.have.property('sizes'); + expect(requested_bids[1]).to.have.property('zones'); + // bid 0 + expect(requested_bids[0].bidId).to.equal('21fe992ca48d55'); + expect(requested_bids[0].sizes).to.not.equal(null); + expect(requested_bids[0].sizes).to.have.lengthOf(1); + expect(requested_bids[0].sizes[0][0]).to.equal(300); + expect(requested_bids[0].sizes[0][1]).to.equal(250); + expect(requested_bids[0].zones).to.equal('1806'); + // bid 1 + expect(requested_bids[1].bidId).to.equal('23kljh54390534'); + expect(requested_bids[1].sizes).to.not.equal(null); + expect(requested_bids[1].sizes).to.have.lengthOf(1); + expect(requested_bids[1].sizes[0][0]).to.equal(728); + expect(requested_bids[1].sizes[0][1]).to.equal(90); + expect(requested_bids[1].zones).to.equal('276'); }); - describe('handling bid response', function () { - it('should return complete bid response adUrl', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '1806' } - }] - }; - - var response = { - cpm: 0.5, - width: 300, - height: 250, - callback_id: '21fe992ca48d55', - ad: 'some.ad.url', - ad_type: 'url', - zone: 1806 - }; - - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); - - sinon.assert.calledOnce(stubAddBidResponse); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-1460505661639-0'); - expect(bidObject1.bidderCode).to.equal('pollux'); - expect(bidObject1.cpm).to.equal(0.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.adUrl).to.have.length.above(1); - }); - - it('should return complete bid response ad (html)', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '1806' } - }] - }; - - var response = { - cpm: 0.5, - width: 300, - height: 250, - callback_id: '21fe992ca48d55', - ad: '', - ad_type: 'html', - zone: 1806 - }; - - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); - - sinon.assert.calledOnce(stubAddBidResponse); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-1460505661639-0'); - expect(bidObject1.bidderCode).to.equal('pollux'); - expect(bidObject1.cpm).to.equal(0.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.ad).to.have.length.above(1); - }); + it('TEST: verify interpretResponse empty', () => { + let bids = spec.interpretResponse(false, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse([], {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse({}, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse(null, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + }); - it('should return no bid response', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '276' } - }] - }; + it('TEST: verify interpretResponse ad_type url', () => { + const serverResponse = { + body: [ + { + bidId: '789s6354sfg856', + cpm: '2.15', + width: '728', + height: '90', + ad: 'http://adn.polluxnetwork.com/zone/276?_plx_prebid=1&_plx_campaign=1125', + ad_type: 'url', + creativeId: '1125', + referrer: 'http://www.example.com' + } + ] + }; + const bids = spec.interpretResponse(serverResponse, {}); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('789s6354sfg856'); + expect(bids[0].cpm).to.equal(2.15); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ttl).to.equal(3600); + expect(bids[0].creativeId).to.equal('1125'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].currency).to.equal('EUR'); + expect(bids[0].referrer).to.equal('http://www.example.com'); + expect(bids[0].adUrl).to.equal('http://adn.polluxnetwork.com/zone/276?_plx_prebid=1&_plx_campaign=1125'); + expect(bids[0]).to.not.have.property('ad'); + }); - var response = { - cpm: null, - width: null, - height: null, - callback_id: null, - ad: null, - zone: null - }; + it('TEST: verify interpretResponse ad_type html', () => { + const serverResponse = { + body: [ + { + bidId: '789s6354sfg856', + cpm: '2.15', + width: '728', + height: '90', + ad: '

I am an ad

', + ad_type: 'html', + creativeId: '1125' + } + ] + }; + const bids = spec.interpretResponse(serverResponse, {}); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('789s6354sfg856'); + expect(bids[0].cpm).to.equal(2.15); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ttl).to.equal(3600); + expect(bids[0].creativeId).to.equal('1125'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].currency).to.equal('EUR'); + expect(bids[0]).to.not.have.property('referrer'); + expect(bids[0]).to.not.have.property('adUrl'); + expect(bids[0].ad).to.equal('

I am an ad

'); + }); - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); + it('TEST: verify url and query params', () => { + const URL = require('url-parse'); + const querystringify = require('querystringify'); + const request = spec.buildRequests(setup_single_bid); + const parsedUrl = new URL('https:' + request.url); + expect(parsedUrl.origin).to.equal('https://adn.polluxnetwork.com'); + expect(parsedUrl.pathname).to.equal('/prebid/v1'); + expect(parsedUrl).to.have.property('query'); + const parsedQuery = querystringify.parse(parsedUrl.query); + expect(parsedQuery).to.have.property('domain').and.to.have.length.above(1); + }); - sinon.assert.calledOnce(stubAddBidResponse); + it('TEST: verify isBidRequestValid', () => { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid(setup_single_bid[0])).to.equal(true); + expect(spec.isBidRequestValid(setup_multi_bid[0])).to.equal(true); + expect(spec.isBidRequestValid(setup_multi_bid[1])).to.equal(true); + }); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + it('TEST: verify bidder code', () => { + expect(spec.code).to.equal('pollux'); + }); - expect(bidPlacementCode1).to.equal(''); - expect(bidObject1.bidderCode).to.equal('pollux'); - }); + it('TEST: verify bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('plx'); }); }); From 71fa70746b54855b54658545b2e5c5c4e11a2c10 Mon Sep 17 00:00:00 2001 From: Matt Probert Date: Fri, 10 Nov 2017 22:55:34 +0100 Subject: [PATCH 62/62] Fix test that hard-coded pbjs global. (#1786) --- test/spec/modules/s2sTesting_spec.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index f829087a967..4ddd7278f4e 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -312,15 +312,15 @@ describe('s2sTesting', function () { const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING; function checkTargeting(bidder) { - var targeting = window.pbjs.bidderSettings[bidder][AST]; + var targeting = window.$$PREBID_GLOBAL$$.bidderSettings[bidder][AST]; var srcTargeting = targeting[targeting.length - 1]; expect(srcTargeting.key).to.equal(`hb_source_${bidder}`); expect(srcTargeting.val).to.be.a('function'); - expect(window.pbjs.bidderSettings[bidder].alwaysUseBid).to.be.true; + expect(window.$$PREBID_GLOBAL$$.bidderSettings[bidder].alwaysUseBid).to.be.true; } function checkNoTargeting(bidder) { - var bs = window.pbjs.bidderSettings; + var bs = window.$$PREBID_GLOBAL$$.bidderSettings; var targeting = bs[bidder] && bs[bidder][AST]; if (!targeting) { expect(targeting).to.be.undefined; @@ -332,22 +332,22 @@ describe('s2sTesting', function () { } function checkTargetingVal(bidResponse, expectedVal) { - var targeting = window.pbjs.bidderSettings[bidResponse.bidderCode][AST]; + var targeting = window.$$PREBID_GLOBAL$$.bidderSettings[bidResponse.bidderCode][AST]; var targetingFunc = targeting[targeting.length - 1].val; expect(targetingFunc(bidResponse)).to.equal(expectedVal); } beforeEach(() => { // set bidderSettings - window.pbjs.bidderSettings = {}; + window.$$PREBID_GLOBAL$$.bidderSettings = {}; }); it('should not set hb_source_ unless testing is on and includeSourceKvp is set', () => { config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus']}}); - expect(window.pbjs.bidderSettings).to.eql({}); + expect(window.$$PREBID_GLOBAL$$.bidderSettings).to.eql({}); config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus'], testing: true}}); - expect(window.pbjs.bidderSettings).to.eql({}); + expect(window.$$PREBID_GLOBAL$$.bidderSettings).to.eql({}); config.setConfig({s2sConfig: { bidders: ['rubicon', 'appnexus'], @@ -357,7 +357,7 @@ describe('s2sTesting', function () { appnexus: {bidSource: {server: 1}} } }}); - expect(window.pbjs.bidderSettings).to.eql({}); + expect(window.$$PREBID_GLOBAL$$.bidderSettings).to.eql({}); config.setConfig({s2sConfig: { bidders: ['rubicon', 'appnexus'], @@ -367,7 +367,7 @@ describe('s2sTesting', function () { appnexus: {includeSourceKvp: true} } }}); - expect(window.pbjs.bidderSettings).to.eql({}); + expect(window.$$PREBID_GLOBAL$$.bidderSettings).to.eql({}); }); it('should set hb_source_ if includeSourceKvp is set', () => {