From 0d6868a3f0b02c2720a633a952d844ce883ebfef Mon Sep 17 00:00:00 2001 From: IHateCodesss Date: Mon, 13 Nov 2017 15:02:00 -0800 Subject: [PATCH] OpenX Video Adapter update to Prebid v1.0 (#1724) * Add video support * remove side-effects in unit tests and update test ad domain * Add mediaType adn vastUrl for video reponse --- modules/openxBidAdapter.js | 138 +++++++++++++++---- modules/openxBidAdapter.md | 18 +++ test/spec/modules/openxBidAdapter_spec.js | 154 ++++++++++++++++++++-- 3 files changed, 275 insertions(+), 35 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index a0bcd2a945f..6f81d0ede84 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,9 +2,9 @@ import { config } from 'src/config'; import {registerBidder} from 'src/adapters/bidderFactory'; import * as utils from 'src/utils'; import {userSync} from 'src/userSync'; -import { BANNER } from 'src/mediaTypes'; +import { BANNER, VIDEO } from 'src/mediaTypes'; -const SUPPORTED_AD_TYPES = [BANNER]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; const BIDDER_VERSION = '2.0.0'; @@ -13,6 +13,11 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function(bid) { + if (bid.mediaType === VIDEO) { + if (typeof bid.params.video !== 'object' || !bid.params.video.url) { + return false; + } + } return !!(bid.params.unit && bid.params.delDomain); }, buildRequests: function(bids) { @@ -22,27 +27,59 @@ export const spec = { return; } - let delDomain = bids[0].params.delDomain; - let configuredBc = bids[0].params.bc; - let bc = configuredBc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`; - - 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); + let requests = []; + let bannerRequests = []; + let videoRequests = []; + let bannerBids = bids.filter(function(bid) { return bid.mediaType === BANNER; }); + let videoBids = bids.filter(function(bid) { return bid.mediaType === VIDEO; }); + + // build banner requests + if (bannerBids.length !== 0) { + let delDomain = bannerBids[0].params.delDomain; + let configuredBc = bannerBids[0].params.bc; + let bc = configuredBc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`; + bannerRequests = [ buildOXRequest(bannerBids, { + 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)]; + } + // build video requests + if (videoBids.length !== 0) { + videoRequests = buildOXVideoRequest(videoBids); + } + + requests = bannerRequests.concat(videoRequests); + return requests; }, interpretResponse: function({body: oxResponseObj}, bidRequest) { let bidResponses = []; + let mediaType = BANNER; + if (bidRequest && bidRequest.payload) { + if (bidRequest.payload.bids) { + mediaType = bidRequest.payload.bids[0].mediaType; + } else if (bidRequest.payload.bid) { + mediaType = bidRequest.payload.bid.mediaType; + } + } + + if (mediaType === VIDEO) { + if (oxResponseObj && oxResponseObj.pixels) { + userSync.registerSync('iframe', 'openx', oxResponseObj.pixels); + } + bidResponses = createVideoBidResponses(oxResponseObj, bidRequest.payload); + return bidResponses; + } + let adUnits = oxResponseObj.ads.ad; if (oxResponseObj.ads && oxResponseObj.ads.pixels) { userSync.registerSync('iframe', BIDDER_CODE, oxResponseObj.ads.pixels); @@ -96,13 +133,13 @@ function createBidResponses(adUnits, {bids, startTime}) { if (adUnit.deal_id) { bidResponse.dealId = adUnit.deal_id; } - // default 5 mins + // default 5 mins bidResponse.ttl = 300; - // true is net, false is gross + // true is net, false is gross bidResponse.netRevenue = true; bidResponse.currency = adUnit.currency; - // additional fields to add + // additional fields to add if (adUnit.tbd) { bidResponse.tbd = adUnit.tbd; } @@ -211,7 +248,7 @@ function formatCustomParms(customKey, customParams) { // if value is an array, join them with commas first value = value.join(','); } - // return customKey=customValue format, escaping + to . and / to _ + // return customKey=customValue format, escaping + to . and / to _ return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') } @@ -265,4 +302,57 @@ function buildOXRequest(bids, oxParams, delDomain) { }; } +function buildOXVideoRequest(bids) { + return bids.map(function(bid) { + let url = 'http://' + bid.params.delDomain + '/v/1.0/avjp'; + let oxVideoParams = generateVideoParameters(bid); + return { + method: 'GET', + url: url, + data: oxVideoParams, + payload: {'bid': bid, 'startTime': new Date()} + }; + }); +} + +function generateVideoParameters(bid) { + let oxVideo = bid.params.video; + let oxVideoParams = { auid: bid.params.unit }; + + Object.keys(oxVideo).forEach(function(key) { + if (key === 'openrtb') { + oxVideoParams[key] = JSON.stringify(oxVideo[key]); + } else { + oxVideoParams[key] = oxVideo[key]; + } + }); + oxVideoParams['be'] = 'true'; + return oxVideoParams; +} + +function createVideoBidResponses(response, {bid, startTime}) { + let bidResponses = []; + + if (response !== undefined && response.vastUrl !== '' && response.pub_rev !== '') { + let bidResponse = {}; + bidResponse.requestId = bid.bidId; + bidResponse.bidderCode = BIDDER_CODE; + // default 5 mins + bidResponse.ttl = 300; + // true is net, false is gross + bidResponse.netRevenue = true; + bidResponse.currency = response.currency; + bidResponse.cpm = Number(response.pub_rev) / 1000; + bidResponse.width = response.width; + bidResponse.height = response.height; + bidResponse.creativeId = response.adid; + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = VIDEO; + + bidResponses.push(bidResponse); + } + + return bidResponses; +} + registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 5b3ad77ce6d..1f04c2fe466 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -16,6 +16,7 @@ Module that connects to OpenX's demand sources { code: 'test-div', sizes: [[728, 90]], // a display size + mediaType: 'banner', bids: [ { bidder: "openx", @@ -26,5 +27,22 @@ Module that connects to OpenX's demand sources } ] }, + { + code: 'video1', + sizes: [[640,480]], + mediaType: 'video', + bids: [ + { + bidder: 'openx', + params: { + unit: '539131525', + delDomain: 'zdo.com', + video: { + url: 'abc.com' + } + } + } + ] + } ]; ``` diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cee521b2921..a03bebf9e0a 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/openxBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; const URLBASE = '/w/1.0/arj'; +const URLBASEVIDEO = '/v/1.0/avjp'; describe('OpenxAdapter', () => { const adapter = newBidder(spec); @@ -21,25 +22,57 @@ describe('OpenxAdapter', () => { 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }; - it('should return true when required params found', () => { + let videoBid = { + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'video': { + 'be': 'true', + 'url': 'abc.com', + 'vtest': '1' + } + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'video', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found for a banner ad', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required params are not passed', () => { + it('should return false when required params are not passed for a banner ad', () => { let bid = Object.assign({}, bid); delete bid.params; bid.params = {'unit': '12345678'}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return true when required params found for a video ad', () => { + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + + it('should return false when required params are not passed for a video ad', () => { + let videoBid = Object.assign({}, videoBid); + delete videoBid.params; + videoBid.params = {}; + expect(spec.isBidRequestValid(videoBid)).to.equal(false); + }); }); - describe('buildRequests', () => { + describe('buildRequests for banner ads', () => { let bidRequests = [{ 'bidder': 'openx', 'params': { @@ -47,6 +80,7 @@ describe('OpenxAdapter', () => { 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -55,13 +89,13 @@ describe('OpenxAdapter', () => { 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'); + expect(request[0].url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); }); it('should have the correct parameters', () => { const request = spec.buildRequests(bidRequests); - const dataParams = request.data; + const dataParams = request[0].data; expect(dataParams.auid).to.exist; expect(dataParams.auid).to.equal('12345678'); @@ -82,7 +116,7 @@ describe('OpenxAdapter', () => { ); const request = spec.buildRequests([bidRequest]); - const dataParams = request.data; + const dataParams = request[0].data; expect(dataParams.tps).to.exist; expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); @@ -101,7 +135,7 @@ describe('OpenxAdapter', () => { ); const request = spec.buildRequests([bidRequest]); - const dataParams = request.data; + const dataParams = request[0].data; expect(dataParams.aumfs).to.exist; expect(dataParams.aumfs).to.equal('1500'); @@ -120,14 +154,50 @@ describe('OpenxAdapter', () => { ); const request = spec.buildRequests([bidRequest]); - const dataParams = request.data; + const dataParams = request[0].data; expect(dataParams.bc).to.exist; expect(dataParams.bc).to.equal('hb_override'); }); }); - describe('interpretResponse', () => { + describe('buildRequests for video', () => { + let bidRequests = [{ + 'bidder': 'openx', + 'mediaType': 'video', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'video': { + 'url': 'abc.com', + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + + it('should send bid request to openx url via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request[0].url).to.equal('http://' + bidRequests[0].params.delDomain + URLBASEVIDEO); + expect(request[0].method).to.equal('GET'); + }); + + it('should have the correct parameters', () => { + const request = spec.buildRequests(bidRequests); + const dataParams = request[0].data; + + expect(dataParams.auid).to.exist; + expect(dataParams.auid).to.equal('12345678'); + expect(dataParams.url).to.exist; + expect(dataParams.url).to.equal('abc.com'); + }); + }); + + describe('interpretResponse for banner ads', () => { let bids = [{ 'bidder': 'openx', 'params': { @@ -135,6 +205,7 @@ describe('OpenxAdapter', () => { 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -203,7 +274,7 @@ describe('OpenxAdapter', () => { }); it('handles nobid responses', () => { - bidResponse = { + let bidResponse = { 'ads': { 'version': 1, @@ -217,4 +288,65 @@ describe('OpenxAdapter', () => { expect(result.length).to.equal(0); }); }); + + describe('interpretResponse for video ads', () => { + let bids = [{ + 'bidder': 'openx', + 'mediaType': 'video', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'video': { + 'url': 'abc.com', + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + let bidRequest = { + method: 'GET', + url: 'url', + data: {}, + payload: {'bid': bids[0], 'startTime': new Date()} + }; + let bidResponse = { + 'pub_rev': '1', + 'width': '640', + 'height': '480', + 'adid': '5678', + 'vastUrl': 'http://testvast.com', + 'pixels': 'http://testpixels.net' + }; + + it('should return correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'openx', + 'cpm': 1, + 'width': '640', + 'height': '480', + 'mediaType': 'video', + 'creativeId': '5678', + 'vastUrl': 'http://testvast.com', + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD' + } + ]; + + let result = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); + }); + + it('handles nobid responses', () => { + let bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; + let result = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(result.length).to.equal(0); + }); + }); });