diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 625d34b0648..b60755f2421 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; import { config } from 'src/config'; +import { BANNER, VIDEO } from 'src/mediaTypes'; const INTEGRATION = 'pbjs_lite_v$prebid.version$'; @@ -73,7 +74,7 @@ utils._each(sizeMap, (item, key) => sizeMap[item] = key); export const spec = { code: 'rubicon', aliases: ['rubiconLite'], - supportedMediaTypes: ['banner', 'video'], + supportedMediaTypes: [BANNER, VIDEO], /** * @param {object} bid * @return boolean @@ -93,8 +94,9 @@ export const spec = { return false; } - if (bid.mediaType === 'video') { - if (typeof params.video !== 'object' || !params.video.size_id) { + if (spec.hasVideoMediaType(bid)) { + // support instream only + if ((utils.deepAccess(bid, `mediaTypes.${VIDEO}`) && utils.deepAccess(bid, `mediaTypes.${VIDEO}.context`) !== 'instream') || typeof params.video !== 'object' || !params.video.size_id) { return false; } } @@ -118,7 +120,7 @@ export const spec = { page_url = bidRequest.params.secure ? page_url.replace(/^http:/i, 'https:') : page_url; - if (bidRequest.mediaType === 'video') { + if (spec.hasVideoMediaType(bidRequest)) { let params = bidRequest.params; let size = parseSizes(bidRequest); @@ -237,6 +239,15 @@ export const spec = { }; }); }, + /** + * Test if bid has mediaType or mediaTypes set for video. + * note: 'mediaType' has been deprecated, however support will remain for a transitional period + * @param {BidRequest} bidRequest + * @returns {boolean} + */ + hasVideoMediaType: function(bidRequest) { + return bidRequest.mediaType === VIDEO || typeof utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'; + }, /** * @param {*} responseObj * @param {BidRequest} bidRequest @@ -252,7 +263,7 @@ export const spec = { } // video ads array is wrapped in an object - if (typeof bidRequest === 'object' && bidRequest.mediaType === 'video' && typeof ads === 'object') { + if (typeof bidRequest === 'object' && spec.hasVideoMediaType(bidRequest) && typeof ads === 'object') { ads = ads[bidRequest.adUnitCode]; } @@ -287,7 +298,7 @@ export const spec = { bid.mediaType = ad.creative_type; } - if (bidRequest.mediaType === 'video') { + if (ad.creative_type === VIDEO) { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; bid.vastUrl = ad.creative_depot_url; @@ -360,17 +371,14 @@ function _renderCreative(script, impId) { function parseSizes(bid) { let params = bid.params; - if (bid.mediaType === 'video') { + if (spec.hasVideoMediaType(bid)) { let size = []; - if (params.video.playerWidth && params.video.playerHeight) { + if (typeof params.video === 'object' && 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 - ) { + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { size = bid.sizes[0]; } return size; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 1c4bccc69c5..107f8d3ea2c 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -17,6 +17,31 @@ describe('the rubicon adapter', () => { function createVideoBidderRequest() { let bid = bidderRequest.bids[0]; + + bid.mediaTypes = { + video: { + context: 'instream' + } + }; + + 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' + } + }; + } + + function createLegacyVideoBidderRequest() { + let bid = bidderRequest.bids[0]; + + // Legacy property (Prebid <1.0) bid.mediaType = 'video'; bid.params.video = { 'language': 'en', @@ -33,12 +58,62 @@ describe('the rubicon adapter', () => { } function createVideoBidderRequestNoVideo() { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream' + }, + }; + bid.params.video = ''; + } + + function createLegacyVideoBidderRequestNoVideo() { let bid = bidderRequest.bids[0]; bid.mediaType = 'video'; bid.params.video = ''; } + function createVideoBidderRequestOutstream() { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'outstream' + }, + }; + 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' + } + }; + } + function createVideoBidderRequestNoPlayer() { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream' + }, + }; + bid.params.video = { + 'language': 'en', + 'p_aso.video.ext.skip': true, + 'p_aso.video.ext.skipdelay': 15, + 'size_id': 201, + 'aeParams': { + 'p_aso.video.ext.skip': '1', + 'p_aso.video.ext.skipdelay': '15' + } + }; + } + + function createLegacyVideoBidderRequestNoPlayer() { let bid = bidderRequest.bids[0]; bid.mediaType = 'video'; bid.params.video = { @@ -81,6 +156,7 @@ describe('the rubicon adapter', () => { referrer: 'localhost' }, adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', @@ -120,8 +196,7 @@ describe('the rubicon adapter', () => { describe('for requests', () => { describe('to fastlane', () => { it('should make a well-formed request objects', () => { - sandbox.stub(Math, 'random').returns(0.1); - + sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -451,6 +526,67 @@ describe('the rubicon adapter', () => { }); describe('for video requests', () => { + it('should make a well-formed video request with legacy mediaType config', () => { + createLegacyVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + let url = request.url; + + expect(url).to.equal('//fastlane-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(INTEGRATION); + expect(post['x_source.tid']).to.equal('d45dd707-a418-42ec-b8a7-b70a6c6fab0b'); + 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(0.01); + 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); + 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'); + }); + it('should make a well-formed video request', () => { createVideoBidderRequest(); @@ -576,17 +712,60 @@ describe('the rubicon adapter', () => { expect(floor).to.equal(3.25); }); - it('should not validate bid request when no video object is passed in', () => { + it('should not validate bid request when a invalid video object is passed in', () => { createVideoBidderRequestNoVideo(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - var floorBidderRequest = clone(bidderRequest); + const bidRequestCopy = clone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - let result = spec.isBidRequestValid(floorBidderRequest.bids[0]); + bidRequestCopy.params.video = {}; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - expect(result).to.equal(false); + bidRequestCopy.params.video = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + bidRequestCopy.params.video = 123; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + bidRequestCopy.params.video = { size_id: '' }; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + delete bidRequestCopy.params.video; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); + + it('should not validate bid request when an invalid video object is passed in with legacy config mediaType', () => { + createLegacyVideoBidderRequestNoVideo(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const bidderRequestCopy = clone(bidderRequest); + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = {}; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = undefined; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = NaN; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + delete bidderRequestCopy.bids[0].params.video; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + }); + + it('should not validate bid request when video is outstream', () => { + createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); }); it('should get size from bid.sizes too', () => { @@ -595,13 +774,55 @@ describe('the rubicon adapter', () => { bidderRequest.auctionStart + 100 ); - var floorBidderRequest = clone(bidderRequest); + const bidRequestCopy = clone(bidderRequest); - let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); - let post = request.data; + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(post.slots[0].width).to.equal(300); - expect(post.slots[0].height).to.equal(250); + expect(request.data.slots[0].width).to.equal(300); + expect(request.data.slots[0].height).to.equal(250); + }); + + it('should get size from bid.sizes too with legacy config mediaType', () => { + createLegacyVideoBidderRequestNoPlayer(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const bidRequestCopy = clone(bidderRequest); + + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + + expect(request.data.slots[0].width).to.equal(300); + expect(request.data.slots[0].height).to.equal(250); + }); + }); + + describe('hasVideoMediaType', () => { + it('should return true if mediaType is true', () => { + createVideoBidderRequest(); + const legacyVideoTypeBidRequest = spec.hasVideoMediaType(bidderRequest.bids[0]); + expect(legacyVideoTypeBidRequest).is.equal(true); + }); + + it('should return false if bidRequest.mediaType is not equal to video', () => { + expect(spec.hasVideoMediaType({ + mediaType: 'banner' + })).is.equal(false); + }); + + it('should return false if bidRequest.mediaType is not defined', () => { + expect(spec.hasVideoMediaType({})).is.equal(false); + }); + + it('should return true if bidRequest.mediaTypes.video object exists', () => { + expect(spec.hasVideoMediaType({ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + } + })).is.equal(true); }); }); });