From db7fdc990ea330144a04e9582263a91a7aebc19a Mon Sep 17 00:00:00 2001 From: David Bridges Date: Wed, 18 Jan 2017 15:34:19 -0800 Subject: [PATCH 1/7] Integrated video bid support into rubicon adapter. Updated adapter.json with new media support. --- adapters.json | 3 +- src/adapters/rubicon.js | 104 +++++++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/adapters.json b/adapters.json index fa5e1d228d6..5edf8f107d0 100644 --- a/adapters.json +++ b/adapters.json @@ -69,7 +69,8 @@ }, { "rubicon": { - "alias": "rubiconLite" + "alias": "rubiconLite", + "supportedMediaTypes": ["video"] } }, { diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 8baccc2dd87..1f36dc0487e 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -45,11 +45,17 @@ utils._each(sizeMap, (item, key) => sizeMap[item] = key); function RubiconAdapter() { function _callBids(bidderRequest) { - var bids = bidderRequest.bids || []; + var bids = bidderRequest.bids || [], + videoFastlaneURL = 'http://optimized-by-adv.rubiconproject.com/v1/auction/video'; bids.forEach(bid => { try { - ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true}); + // Video endpoint only accepts POST calls + if (bid.mediaType === 'video') { + ajax(videoFastlaneURL, bidCallback, buildVideoRequestPayload(bid,bidderRequest), {withCredentials: true}); + } else { + ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true}); + } } catch(err) { utils.logError('Error sending rubicon request for placement code ' + bid.placementCode, null, err); addErrorBid(); @@ -77,6 +83,64 @@ function RubiconAdapter() { }); } + function buildVideoRequestPayload(bid,bidderRequest) { + bid.startTime = new Date().getTime(); + + let oParams = bid.params, + TIMEOUT_BUFFER = 500; + + if(!oParams || typeof oParams.video !== "object") { + throw "Invalid Video Bid"; + } + + let postData = { + page_url: !oParams.pageUrl ? utils.getTopWindowUrl() : oParams.pageUrl, + resolution: window.screen.width +'x'+ window.screen.height, + account_id: oParams.accountId, + integration: 'pbjs.lite', + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart - TIMEOUT_BUFFER), + stash_creatives: true, + ae_pass_through_parameters: oParams.video.aeParams, + slots: [] + }; + + // Define the slot object + let slotData = { + site_id: oParams.siteId, + zone_id: oParams.zoneId, + position: oParams.position || 'btf', + floor: 0.01, + element_id: bid.placementCode, + name: oParams.name, + language: oParams.video.language, + height: oParams.video.playerHeight, + width: oParams.video.playerWidth + }; + + // check and add inventory, keywords, visitor and size_id data + if(Array.isArray(oParams.sizes) && oParams.sizes.length > 0) { + slotData.size_id = oParams.sizes[0]; + } else { + throw "Invalid Video Bid - Invalid Size!"; + } + + if(oParams.inventory && typeof oParams.inventory === "object") { + slotData.inventory = oParams.inventory; + } + + if(oParams.keywords && typeof Array.isArray(oParams.keywords)) { + slotData.keywords = oParams.keywords; + } + + if(oParams.visitor && typeof oParams.visitor === "object") { + slotData.visitor = oParams.visitor; + } + + postData.slots.push(slotData); + + return(JSON.stringify(postData)); + } + function buildOptimizedCall(bid) { bid.startTime = new Date().getTime(); @@ -152,22 +216,30 @@ function RubiconAdapter() { function handleRpCB(responseText, bidRequest) { let responseObj = JSON.parse(responseText); // can throw + var ads = responseObj.ads, + adResponseKey = bidRequest.placementCode; - if( - typeof responseObj !== 'object' || - responseObj.status !== 'ok' || - !Array.isArray(responseObj.ads) || - responseObj.ads.length < 1 - ) { + // check overall response + if(typeof responseObj !== 'object' || responseObj.status !== 'ok') { throw 'bad response'; } - var ads = responseObj.ads; + // video ads array is wrapped in an object + if (bidRequest.mediaType === 'video') { + if(typeof ads === 'object') { + ads = ads[adResponseKey]; + } + } + + // check the ad response + if(!Array.isArray(ads) || ads.length < 1) { + throw 'invalid ad response'; + } // if there are multiple ads, sort by CPM ads = ads.sort(_adCpmSort); - ads.forEach(function (ad) { + ads.forEach(ad => { if(ad.status !== 'ok') { throw 'bad ad status'; } @@ -178,9 +250,17 @@ function RubiconAdapter() { bid.creative_id = ad.ad_id; bid.bidderCode = bidRequest.bidder; bid.cpm = ad.cpm || 0; - bid.ad = _renderCreative(ad.script, ad.impression_id); - [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); bid.dealId = ad.deal; + if (bidRequest.mediaType === 'video') { + bid.width = bidRequest.params.video.playerWidth; + bid.height = bidRequest.params.video.playerHeight; + bid.vastUrl = ad.creative_depot_url; + bid.impression_id = ad.impression_id; + } else { + bid.ad = _renderCreative(ad.script, ad.impression_id); + [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); + } + try { bidmanager.addBidResponse(bidRequest.placementCode, bid); From d1d1b704b46437a2bc3127b582c112046900652e Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Fri, 20 Jan 2017 10:29:20 -0700 Subject: [PATCH 2/7] added a few stylistic and consolidation changes to rubicon adapter --- src/adapters/rubicon.js | 81 ++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 1f36dc0487e..f1aaf5f1407 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -6,6 +6,13 @@ import { ajax } from 'src/ajax'; import { STATUS } from 'src/constants'; const RUBICON_BIDDER_CODE = 'rubicon'; +const INTEGRATION = 'pbjs.lite'; + +// use protocol relative urls for http or https +const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json'; +const VIDEO_ENDPOINT = '//optimized-by-adv.rubiconproject.com/v1/auction/video'; + +const TIMEOUT_BUFFER = 500; var sizeMap = { 1:'468x60', @@ -45,14 +52,13 @@ utils._each(sizeMap, (item, key) => sizeMap[item] = key); function RubiconAdapter() { function _callBids(bidderRequest) { - var bids = bidderRequest.bids || [], - videoFastlaneURL = 'http://optimized-by-adv.rubiconproject.com/v1/auction/video'; + var bids = bidderRequest.bids || []; bids.forEach(bid => { try { // Video endpoint only accepts POST calls if (bid.mediaType === 'video') { - ajax(videoFastlaneURL, bidCallback, buildVideoRequestPayload(bid,bidderRequest), {withCredentials: true}); + ajax(VIDEO_ENDPOINT, bidCallback, buildVideoRequestPayload(bid, bidderRequest), {withCredentials: true}); } else { ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true}); } @@ -83,57 +89,60 @@ function RubiconAdapter() { }); } - function buildVideoRequestPayload(bid,bidderRequest) { + function _getScreenResolution() { + return [window.screen.width, window.screen.height].join('x'); + } + + function buildVideoRequestPayload(bid, bidderRequest) { bid.startTime = new Date().getTime(); - let oParams = bid.params, - TIMEOUT_BUFFER = 500; + let params = bid.params; - if(!oParams || typeof oParams.video !== "object") { - throw "Invalid Video Bid"; + if(!params || typeof params.video !== 'object') { + throw 'Invalid Video Bid'; } let postData = { - page_url: !oParams.pageUrl ? utils.getTopWindowUrl() : oParams.pageUrl, - resolution: window.screen.width +'x'+ window.screen.height, - account_id: oParams.accountId, - integration: 'pbjs.lite', + page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, + resolution: _getScreenResolution(), + account_id: params.accountId, + integration: INTEGRATION, timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart - TIMEOUT_BUFFER), stash_creatives: true, - ae_pass_through_parameters: oParams.video.aeParams, + ae_pass_through_parameters: params.video.aeParams, slots: [] }; // Define the slot object let slotData = { - site_id: oParams.siteId, - zone_id: oParams.zoneId, - position: oParams.position || 'btf', + site_id: params.siteId, + zone_id: params.zoneId, + position: params.position || 'btf', floor: 0.01, element_id: bid.placementCode, - name: oParams.name, - language: oParams.video.language, - height: oParams.video.playerHeight, - width: oParams.video.playerWidth + name: bid.placementCode, + language: params.video.language, + height: params.video.playerHeight, + width: params.video.playerWidth }; // check and add inventory, keywords, visitor and size_id data - if(Array.isArray(oParams.sizes) && oParams.sizes.length > 0) { - slotData.size_id = oParams.sizes[0]; + if(Array.isArray(params.sizes) && params.sizes.length > 0) { + slotData.size_id = params.sizes[0]; } else { throw "Invalid Video Bid - Invalid Size!"; } - if(oParams.inventory && typeof oParams.inventory === "object") { - slotData.inventory = oParams.inventory; + if(params.inventory && typeof params.inventory === 'object') { + slotData.inventory = params.inventory; } - if(oParams.keywords && typeof Array.isArray(oParams.keywords)) { - slotData.keywords = oParams.keywords; + if(params.keywords && Array.isArray(params.keywords)) { + slotData.keywords = params.keywords; } - if(oParams.visitor && typeof oParams.visitor === "object") { - slotData.visitor = oParams.visitor; + if(params.visitor && typeof params.visitor === 'object') { + slotData.visitor = params.visitor; } postData.slots.push(slotData); @@ -177,8 +186,8 @@ function RubiconAdapter() { 'alt_size_ids', parsedSizes.slice(1).join(',') || undefined, 'p_pos', position, 'rp_floor', '0.01', - 'tk_flint', 'pbjs.lite', - 'p_screen_res', window.screen.width +'x'+ window.screen.height, + 'tk_flint', INTEGRATION, + 'p_screen_res', _getScreenResolution(), 'kw', keywords, 'tk_user_key', userId ]; @@ -200,7 +209,7 @@ function RubiconAdapter() { (memo, curr, index) => index % 2 === 0 && queryString[index + 1] !== undefined ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, - '//fastlane.rubiconproject.com/a/api/fastlane.json?' // use protocol relative link for http or https + FASTLANE_ENDPOINT + '?' ).slice(0, -1); // remove trailing & } @@ -215,8 +224,8 @@ function RubiconAdapter() { `; function handleRpCB(responseText, bidRequest) { - let responseObj = JSON.parse(responseText); // can throw - var ads = responseObj.ads, + var responseObj = JSON.parse(responseText), // can throw + ads = responseObj.ads, adResponseKey = bidRequest.placementCode; // check overall response @@ -225,10 +234,8 @@ function RubiconAdapter() { } // video ads array is wrapped in an object - if (bidRequest.mediaType === 'video') { - if(typeof ads === 'object') { - ads = ads[adResponseKey]; - } + if (bidRequest.mediaType === 'video' && typeof ads === 'object') { + ads = ads[adResponseKey]; } // check the ad response From 4bdc6884e6b604ea8b711cbc34ea339bda008720 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Mon, 23 Jan 2017 10:05:54 -0700 Subject: [PATCH 3/7] allow rubicon adapter to get video size from adunit and fix timeout --- src/adapters/rubicon.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index f1aaf5f1407..44992d992b5 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -102,12 +102,27 @@ function RubiconAdapter() { throw 'Invalid Video Bid'; } + let size; + if(params.video.playerWidth && params.video.playerHeight) { + size = [ + params.video.playerWidth, + params.video.playerHeight + ]; + } else if( + Array.isArray(bid.sizes) && bid.sizes.length > 0 && + Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1 + ) { + size = bid.sizes[0]; + } else { + throw "Invalid Video Bid - No size provided"; + } + let postData = { page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, resolution: _getScreenResolution(), account_id: params.accountId, integration: INTEGRATION, - timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart - TIMEOUT_BUFFER), + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), stash_creatives: true, ae_pass_through_parameters: params.video.aeParams, slots: [] @@ -122,15 +137,15 @@ function RubiconAdapter() { element_id: bid.placementCode, name: bid.placementCode, language: params.video.language, - height: params.video.playerHeight, - width: params.video.playerWidth + width: size[0], + height: size[1] }; // check and add inventory, keywords, visitor and size_id data - if(Array.isArray(params.sizes) && params.sizes.length > 0) { - slotData.size_id = params.sizes[0]; + if(params.video.size_id) { + slotData.size_id = params.video.size_id; } else { - throw "Invalid Video Bid - Invalid Size!"; + throw "Invalid Video Bid - Invalid Ad Type!"; } if(params.inventory && typeof params.inventory === 'object') { From 37edf0f4aa56e544818e415938c9a111eb33ebbb Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Mon, 23 Jan 2017 10:06:12 -0700 Subject: [PATCH 4/7] add unit tests for rubicon video --- test/spec/adapters/rubicon_spec.js | 670 ++++++++++++++++++----------- 1 file changed, 414 insertions(+), 256 deletions(-) diff --git a/test/spec/adapters/rubicon_spec.js b/test/spec/adapters/rubicon_spec.js index ff3a7050249..be75bae28e6 100644 --- a/test/spec/adapters/rubicon_spec.js +++ b/test/spec/adapters/rubicon_spec.js @@ -13,12 +13,30 @@ describe('the rubicon adapter', () => { adUnit, bidderRequest; + function createVideoBidderRequest() { + let bid = bidderRequest.bids[0]; + bid.mediaType = 'video'; + bid.params.video = { + 'language': 'en', + 'p_aso.video.ext.skip': true, + 'p_aso.video.ext.skipdelay': 15, + 'playerHeight': 320, + 'playerWidth': 640, + 'size_id': 201, + 'aeParams': { + 'p_aso.video.ext.skip': '1', + 'p_aso.video.ext.skipdelay': '15' + } + }; + } + beforeEach(() => { sandbox = sinon.sandbox.create(); adUnit = { code: '/19968336/header-bid-tag-0', sizes: [[300, 250], [320, 50]], + mediaType: 'video', bids: [ { bidder: 'rubicon', @@ -75,6 +93,7 @@ describe('the rubicon adapter', () => { } ], start: 1472239426002, + auctionStart: 1472239426000, timeout: 5000 }; @@ -105,6 +124,9 @@ describe('the rubicon adapter', () => { expect(bidderRequest).to.have.deep.property('bids[0]') .to.have.property('bidder', 'rubicon'); + expect(bidderRequest).to.have.deep.property('bids[0]') + .to.have.property('mediaType', 'video'); + expect(bidderRequest).to.have.deep.property('bids[0]') .to.have.property('placementCode', adUnit.code); @@ -156,7 +178,7 @@ describe('the rubicon adapter', () => { let rubiconAdapter; - describe('requests', () => { + describe('for requests', () => { let xhr, bids; @@ -177,84 +199,160 @@ describe('the rubicon adapter', () => { xhr.restore(); }); - it('should make a well-formed optimized request', () => { - - rubiconAdapter.callBids(bidderRequest); - - let request = xhr.requests[0]; - - let [path, query] = request.url.split('?'); - query = parseQuery(query); - - expect(path).to.equal( - '//fastlane.rubiconproject.com/a/api/fastlane.json' - ); - - let expectedQuery = { - 'account_id': '14062', - 'site_id': '70608', - 'zone_id': '335918', - 'size_id': '15', - 'alt_size_ids': '43', - 'p_pos': 'atf', - 'rp_floor': '0.01', - 'tk_flint': 'pbjs.lite', - 'p_screen_res': /\d+x\d+/, - 'tk_user_key': '12346', - 'kw': 'a,b,c', - 'tg_v.ucat': 'new', - 'tg_v.lastsearch': 'iphone', - 'tg_i.rating': '5-star', - 'tg_i.prodtype': 'tech', - 'rf': 'localhost' - }; - - // test that all values above are both present and correct - Object.keys(expectedQuery).forEach(key => { - let value = expectedQuery[key]; - if(value instanceof RegExp) { - expect(query[key]).to.match(value); - } else { - expect(query[key]).to.equal(value); - } + describe("to fastlane", () => { + + it('should make a well-formed request', () => { + + rubiconAdapter.callBids(bidderRequest); + + let request = xhr.requests[0]; + + let [path, query] = request.url.split('?'); + query = parseQuery(query); + + expect(path).to.equal( + '//fastlane.rubiconproject.com/a/api/fastlane.json' + ); + + let expectedQuery = { + 'account_id': '14062', + 'site_id': '70608', + 'zone_id': '335918', + 'size_id': '15', + 'alt_size_ids': '43', + 'p_pos': 'atf', + 'rp_floor': '0.01', + 'tk_flint': 'pbjs.lite', + 'p_screen_res': /\d+x\d+/, + 'tk_user_key': '12346', + 'kw': 'a,b,c', + 'tg_v.ucat': 'new', + 'tg_v.lastsearch': 'iphone', + 'tg_i.rating': '5-star', + 'tg_i.prodtype': 'tech', + 'rf': 'localhost' + }; + + // test that all values above are both present and correct + Object.keys(expectedQuery).forEach(key => { + let value = expectedQuery[key]; + if(value instanceof RegExp) { + expect(query[key]).to.match(value); + } else { + expect(query[key]).to.equal(value); + } + }); + + expect(query).to.have.property('rand'); + }); - expect(query).to.have.property('rand'); + it('should use rubicon sizes if present', () => { - }); + var sizesBidderRequest = clone(bidderRequest); + sizesBidderRequest.bids[0].params.sizes = [55, 57, 59]; - it('should use rubicon sizes if present', () => { + rubiconAdapter.callBids(sizesBidderRequest); - var sizesBidderRequest = clone(bidderRequest); - sizesBidderRequest.bids[0].params.sizes = [55, 57, 59]; + let query = parseQuery(xhr.requests[0].url.split('?')[1]); - rubiconAdapter.callBids(sizesBidderRequest); + expect(query['size_id']).to.equal('55'); + expect(query['alt_size_ids']).to.equal('57,59'); - let query = parseQuery(xhr.requests[0].url.split('?')[1]); + }); + + it('should not send a request and register an error bid if no valid sizes', () => { + + var sizesBidderRequest = clone(bidderRequest); + sizesBidderRequest.bids[0].sizes = [[620,250],[300,251]]; + + rubiconAdapter.callBids(sizesBidderRequest); - expect(query['size_id']).to.equal('55'); - expect(query['alt_size_ids']).to.equal('57,59'); + expect(xhr.requests.length).to.equal(0); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + + }); }); - it('should not send a request and register an error bid if no valid sizes', () => { + describe('for video requests', () => { - var sizesBidderRequest = clone(bidderRequest); - sizesBidderRequest.bids[0].sizes = [[620,250],[300,251]]; + beforeEach(() => { + createVideoBidderRequest(); - rubiconAdapter.callBids(sizesBidderRequest); + sandbox.stub(Date, "now", () => + bidderRequest.auctionStart + 100 + ); + }); - expect(xhr.requests.length).to.equal(0); + it('should make a well-formed video request', () => { - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + rubiconAdapter.callBids(bidderRequest); + + let request = xhr.requests[0]; + + let url = request.url; + let post = JSON.parse(request.requestBody); + + expect(url).to.equal('//optimized-by-adv.rubiconproject.com/v1/auction/video'); + + expect(post).to.have.property('page_url').that.is.a('string'); + expect(post.resolution).to.match(/\d+x\d+/); + expect(post.account_id).to.equal('14062') + expect(post.integration).to.equal('pbjs.lite'); + expect(post).to.have.property('timeout').that.is.a('number'); + expect(post.timeout < 5000).to.equal(true); + expect(post.stash_creatives).to.equal(true); + + expect(post).to.have.property('ae_pass_through_parameters'); + expect(post.ae_pass_through_parameters) + .to.have.property('p_aso.video.ext.skip') + .that.equals('1'); + expect(post.ae_pass_through_parameters) + .to.have.property('p_aso.video.ext.skipdelay') + .that.equals('15'); + + expect(post).to.have.property('slots') + .with.length.of(1); + + let slot = post.slots[0]; + + expect(slot.site_id).to.equal('70608'); + expect(slot.zone_id).to.equal('335918'); + expect(slot.position).to.equal('atf'); + expect(slot.floor).to.equal(.01); + expect(slot.element_id).to.equal(bidderRequest.bids[0].placementCode); + expect(slot.name).to.equal(bidderRequest.bids[0].placementCode); + expect(slot.language).to.equal('en'); + expect(slot.width).to.equal(640); + expect(slot.height).to.equal(320); + expect(slot.size_id).to.equal(201); + + expect(slot).to.have.property('inventory').that.is.an('object'); + expect(slot.inventory).to.have.property('rating').that.equals('5-star'); + expect(slot.inventory).to.have.property('prodtype').that.equals('tech'); + + expect(slot).to.have.property('keywords') + .that.is.an('array') + .of.length(3) + .that.deep.equals(['a', 'b', 'c']); + + expect(slot).to.have.property('visitor').that.is.an('object'); + expect(slot.visitor).to.have.property('ucat').that.equals('new'); + expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); + + }) }); }); + + describe('response handler', () => { let bids, server, @@ -278,217 +376,277 @@ describe('the rubicon adapter', () => { server.restore(); }); - it('should handle a success response and sort by cpm', () => { - - server.respondWith(JSON.stringify({ - "status": "ok", - "account_id": 14062, - "site_id": 70608, - "zone_id": 530022, - "size_id": 15, - "alt_size_ids": [ - 43 - ], - "tracking": "", - "inventory": {}, - "ads": [ - { - "status": "ok", - "impression_id": "153dc240-8229-4604-b8f5-256933b9374c", - "size_id": "15", - "ad_id": "6", - "advertiser": 7, - "network": 8, - "creative_id": 9, - "type": "script", - "script": "alert('foo')", - "campaign_id": 10, - "cpm": 0.811, - "targeting": [ + describe('for fastlane', () => { + + it('should handle a success response and sort by cpm', () => { + + server.respondWith(JSON.stringify({ + "status": "ok", + "account_id": 14062, + "site_id": 70608, + "zone_id": 530022, + "size_id": 15, + "alt_size_ids": [ + 43 + ], + "tracking": "", + "inventory": {}, + "ads": [ + { + "status": "ok", + "impression_id": "153dc240-8229-4604-b8f5-256933b9374c", + "size_id": "15", + "ad_id": "6", + "advertiser": 7, + "network": 8, + "creative_id": 9, + "type": "script", + "script": "alert('foo')", + "campaign_id": 10, + "cpm": 0.811, + "targeting": [ + { + "key": "rpfl_14062", + "values": [ + "15_tier_all_test" + ] + } + ] + }, + { + "status": "ok", + "impression_id": "153dc240-8229-4604-b8f5-256933b9374d", + "size_id": "43", + "ad_id": "7", + "advertiser": 7, + "network": 8, + "creative_id": 9, + "type": "script", + "script": "alert('foo')", + "campaign_id": 10, + "cpm": 0.911, + "targeting": [ + { + "key": "rpfl_14062", + "values": [ + "15_tier_all_test" + ] + } + ] + } + ] + })); + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + expect(bidManager.addBidResponse.calledTwice).to.equal(true); + + expect(bids).to.be.lengthOf(2); + + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + 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].ad).to.contain(`alert('foo')`) + .and.to.contain(``) + .and.to.contain(`
`); + + expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + 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].ad).to.contain(`alert('foo')`) + .and.to.contain(``) + .and.to.contain(`
`); + }); + + it('should be fine with a CPM of 0', () => { + server.respondWith(JSON.stringify({ + "status": "ok", + "account_id": 14062, + "site_id": 70608, + "zone_id": 530022, + "size_id": 15, + "alt_size_ids": [ + 43 + ], + "tracking": "", + "inventory": {}, + "ads": [{ + "status": "ok", + "cpm": 0, + "size_id": 15 + }] + })); + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + }); + + it('should handle an error with no ads returned', () => { + server.respondWith(JSON.stringify({ + "status": "ok", + "account_id": 14062, + "site_id": 70608, + "zone_id": 530022, + "size_id": 15, + "alt_size_ids": [ + 43 + ], + "tracking": "", + "inventory": {}, + "ads": [] + })); + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + }); + + it('should handle an error with bad status', () => { + server.respondWith(JSON.stringify({ + "status": "ok", + "account_id": 14062, + "site_id": 70608, + "zone_id": 530022, + "size_id": 15, + "alt_size_ids": [ + 43 + ], + "tracking": "", + "inventory": {}, + "ads": [{ + "status": "not_ok", + }] + })); + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + }); + + it('should handle an error because of malformed json response', () => { + server.respondWith("{test{"); + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + }); + + it('should not register an error bid when a success call to addBidResponse throws an error', () => { + + server.respondWith(JSON.stringify({ + "status": "ok", + "account_id": 14062, + "site_id": 70608, + "zone_id": 530022, + "size_id": 15, + "alt_size_ids": [ + 43 + ], + "tracking": "", + "inventory": {}, + "ads": [{ + "status": "ok", + "cpm": .8, + "size_id": 15 + }] + })); + + addBidResponseAction = function() { + throw new Error("test error"); + }; + + rubiconAdapter.callBids(bidderRequest); + + server.respond(); + + // was calling twice for same bid, but should only call once + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids).to.be.lengthOf(1); + + }); + + }); + + describe('for video', () => { + + beforeEach(() => { + createVideoBidderRequest(); + }); + + it('should register a successful bid', () => { + + server.respondWith(JSON.stringify({ + "status": "ok", + "ads": { + "/19968336/header-bid-tag-0": [ { - "key": "rpfl_14062", - "values": [ - "15_tier_all_test" - ] + "status": "ok", + "cpm": 1, + "tier": "tier0200", + "targeting": { + "rpfl_8000": "201_tier0200", + "rpfl_elemid": "/19968336/header-bid-tag-0" + }, + "impression_id": "a40fe16e-d08d-46a9-869d-2e1573599e0c", + "site_id": 88888, + "zone_id": 54321, + "creative_type": "video", + "creative_depot_url": "https://optimized-by-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml", + "ad_id": 999999, + "size_id": 201, + "advertiser": 12345 } ] }, - { - "status": "ok", - "impression_id": "153dc240-8229-4604-b8f5-256933b9374d", - "size_id": "43", - "ad_id": "7", - "advertiser": 7, - "network": 8, - "creative_id": 9, - "type": "script", - "script": "alert('foo')", - "campaign_id": 10, - "cpm": 0.911, - "targeting": [ - { - "key": "rpfl_14062", - "values": [ - "15_tier_all_test" - ] - } - ] - } - ] - })); - - rubiconAdapter.callBids(bidderRequest); - - server.respond(); - - expect(bidManager.addBidResponse.calledTwice).to.equal(true); - - expect(bids).to.be.lengthOf(2); - - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - 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].ad).to.contain(`alert('foo')`) - .and.to.contain(``) - .and.to.contain(`
`); - - expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - 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].ad).to.contain(`alert('foo')`) - .and.to.contain(``) - .and.to.contain(`
`); - }); + "account_id": 7780 + })); - it('should be fine with a CPM of 0', () => { - server.respondWith(JSON.stringify({ - "status": "ok", - "account_id": 14062, - "site_id": 70608, - "zone_id": 530022, - "size_id": 15, - "alt_size_ids": [ - 43 - ], - "tracking": "", - "inventory": {}, - "ads": [{ - "status": "ok", - "cpm": 0, - "size_id": 15 - }] - })); - - rubiconAdapter.callBids(bidderRequest); - - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - }); - - it('should handle an error with no ads returned', () => { - server.respondWith(JSON.stringify({ - "status": "ok", - "account_id": 14062, - "site_id": 70608, - "zone_id": 530022, - "size_id": 15, - "alt_size_ids": [ - 43 - ], - "tracking": "", - "inventory": {}, - "ads": [] - })); - - rubiconAdapter.callBids(bidderRequest); - - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); - - it('should handle an error with bad status', () => { - server.respondWith(JSON.stringify({ - "status": "ok", - "account_id": 14062, - "site_id": 70608, - "zone_id": 530022, - "size_id": 15, - "alt_size_ids": [ - 43 - ], - "tracking": "", - "inventory": {}, - "ads": [{ - "status": "not_ok", - }] - })); - - rubiconAdapter.callBids(bidderRequest); - - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); + rubiconAdapter.callBids(bidderRequest); - it('should handle an error because of malformed json response', () => { - server.respondWith("{test{"); + server.respond(); - rubiconAdapter.callBids(bidderRequest); + // was calling twice for same bid, but should only call once + expect(bidManager.addBidResponse.calledOnce).to.equal(true); - server.respond(); + expect(bids).to.be.lengthOf(1); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].creative_id).to.equal(999999); + expect(bids[0].cpm).to.equal(1); + expect(bids[0].vastUrl).to.equal( + 'https://optimized-by-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' + ); + expect(bids[0].impression_id).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); - it('should not register an error bid when a success call to addBidResponse throws an error', () => { - - server.respondWith(JSON.stringify({ - "status": "ok", - "account_id": 14062, - "site_id": 70608, - "zone_id": 530022, - "size_id": 15, - "alt_size_ids": [ - 43 - ], - "tracking": "", - "inventory": {}, - "ads": [{ - "status": "ok", - "cpm": .8, - "size_id": 15 - }] - })); - - addBidResponseAction = function() { - throw new Error("test error"); - }; - - rubiconAdapter.callBids(bidderRequest); - - server.respond(); - - // was calling twice for same bid, but should only call once - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids).to.be.lengthOf(1); + }) }); - }) + }); }); From 6659d512e7326edcb9dd968afdb0b2d2a31171cf Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Tue, 31 Jan 2017 10:31:41 -0700 Subject: [PATCH 5/7] added prebid versioning to rubicon adapter --- src/adapters/rubicon.js | 10 +++++++--- test/spec/adapters/rubicon_spec.js | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 44992d992b5..31eb7e7f625 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -6,7 +6,11 @@ import { ajax } from 'src/ajax'; import { STATUS } from 'src/constants'; const RUBICON_BIDDER_CODE = 'rubicon'; -const INTEGRATION = 'pbjs.lite'; + +// use deferred function call since version isn't defined yet at this point +function getIntegration() { + return 'pbjs_lite_' + $$PREBID_GLOBAL$$.version; +} // use protocol relative urls for http or https const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json'; @@ -121,7 +125,7 @@ function RubiconAdapter() { page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, resolution: _getScreenResolution(), account_id: params.accountId, - integration: INTEGRATION, + integration: getIntegration(), timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), stash_creatives: true, ae_pass_through_parameters: params.video.aeParams, @@ -201,7 +205,7 @@ function RubiconAdapter() { 'alt_size_ids', parsedSizes.slice(1).join(',') || undefined, 'p_pos', position, 'rp_floor', '0.01', - 'tk_flint', INTEGRATION, + 'tk_flint', getIntegration(), 'p_screen_res', _getScreenResolution(), 'kw', keywords, 'tk_user_key', userId diff --git a/test/spec/adapters/rubicon_spec.js b/test/spec/adapters/rubicon_spec.js index be75bae28e6..5e76bc3a3da 100644 --- a/test/spec/adapters/rubicon_spec.js +++ b/test/spec/adapters/rubicon_spec.js @@ -7,6 +7,8 @@ import {parse as parseQuery} from 'querystring'; var CONSTANTS = require('src/constants.json'); +const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid + describe('the rubicon adapter', () => { let sandbox, @@ -222,7 +224,7 @@ describe('the rubicon adapter', () => { 'alt_size_ids': '43', 'p_pos': 'atf', 'rp_floor': '0.01', - 'tk_flint': 'pbjs.lite', + 'tk_flint': INTEGRATION, 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -302,7 +304,7 @@ describe('the rubicon adapter', () => { expect(post).to.have.property('page_url').that.is.a('string'); expect(post.resolution).to.match(/\d+x\d+/); expect(post.account_id).to.equal('14062') - expect(post.integration).to.equal('pbjs.lite'); + expect(post.integration).to.equal(INTEGRATION); expect(post).to.have.property('timeout').that.is.a('number'); expect(post.timeout < 5000).to.equal(true); expect(post.stash_creatives).to.equal(true); From c1725e53a8329cfa435e18eb6955a8e647fb0e7c Mon Sep 17 00:00:00 2001 From: David Bridges Date: Thu, 2 Feb 2017 11:44:40 -0800 Subject: [PATCH 6/7] Set bidurl to impression_id and fixed small bug with correlator argument in adserver.js --- src/adapters/rubicon.js | 2 +- src/adserver.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 31eb7e7f625..8c737238adf 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -280,7 +280,7 @@ function RubiconAdapter() { if (bidRequest.mediaType === 'video') { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; - bid.vastUrl = ad.creative_depot_url; + bid.vastUrl = ad.impression_id; bid.impression_id = ad.impression_id; } else { bid.ad = _renderCreative(ad.script, ad.impression_id); diff --git a/src/adserver.js b/src/adserver.js index fce4695363f..b4ec8a22a63 100644 --- a/src/adserver.js +++ b/src/adserver.js @@ -32,7 +32,7 @@ exports.dfpAdserver = function (options, urlComponents) { var bid = adserver.getWinningBidByCode(); this.urlComponents.search.description_url = encodeURIComponent(bid.descriptionUrl); this.urlComponents.search.cust_params = getCustomParams(bid.adserverTargeting); - this.urlComponents.correlator = Date.now(); + this.urlComponents.search.correlator = Date.now(); }; adserver.verifyAdserverTag = function() { From 1f156692844617a5f70cb56d4f732fa8d4d4829d Mon Sep 17 00:00:00 2001 From: David Bridges Date: Fri, 3 Feb 2017 12:29:34 -0700 Subject: [PATCH 7/7] set new bid.descriptionUrl option to impression_id and updated unit test --- src/adapters/rubicon.js | 3 ++- test/spec/adapters/rubicon_spec.js | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 8c737238adf..838d8928dfa 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -280,7 +280,8 @@ function RubiconAdapter() { if (bidRequest.mediaType === 'video') { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; - bid.vastUrl = ad.impression_id; + bid.vastUrl = ad.creative_depot_url; + bid.descriptionUrl = ad.impression_id; bid.impression_id = ad.impression_id; } else { bid.ad = _renderCreative(ad.script, ad.impression_id); diff --git a/test/spec/adapters/rubicon_spec.js b/test/spec/adapters/rubicon_spec.js index 5e76bc3a3da..a875def399f 100644 --- a/test/spec/adapters/rubicon_spec.js +++ b/test/spec/adapters/rubicon_spec.js @@ -114,7 +114,7 @@ describe('the rubicon adapter', () => { sandbox.stub(rubiconAdapter, 'callBids'); adapterManager.callBids({ - adUnits: [clone(adUnit)] + adUnits: [clone(adUnit)] }); let bidderRequest = rubiconAdapter.callBids.getCall(0).args[0]; @@ -172,7 +172,7 @@ describe('the rubicon adapter', () => { ordering = masSizeOrdering([[120, 600], [320, 50], [160,600], [640, 480],[336, 280], [200, 600], [728, 90]]); expect(ordering).to.deep.equal([2, 9, 8, 16, 43, 65, 126]); - }) + }); }); @@ -303,7 +303,7 @@ describe('the rubicon adapter', () => { expect(post).to.have.property('page_url').that.is.a('string'); expect(post.resolution).to.match(/\d+x\d+/); - expect(post.account_id).to.equal('14062') + expect(post.account_id).to.equal('14062'); expect(post.integration).to.equal(INTEGRATION); expect(post).to.have.property('timeout').that.is.a('number'); expect(post.timeout < 5000).to.equal(true); @@ -346,7 +346,7 @@ describe('the rubicon adapter', () => { expect(slot.visitor).to.have.property('ucat').that.equals('new'); expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); - }) + }); }); @@ -639,12 +639,13 @@ describe('the rubicon adapter', () => { expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].creative_id).to.equal(999999); expect(bids[0].cpm).to.equal(1); + expect(bids[0].descriptionUrl).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); expect(bids[0].vastUrl).to.equal( 'https://optimized-by-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' ); expect(bids[0].impression_id).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); - }) + }); });