From fed1688b0d006ab73455716caf03bf433a438697 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Tue, 3 Oct 2017 12:38:14 -0600 Subject: [PATCH] rubicon converted to bidderFactory (#1624) * rubicon converted to bidderFactory plus few updates to factory * name change updates to bidderFactory * added transactionId => tid to rubicon request --- modules/rubiconBidAdapter.js | 534 +++++++++----------- src/adapters/bidderFactory.js | 26 +- test/spec/modules/rubiconBidAdapter_spec.js | 184 +++---- 3 files changed, 324 insertions(+), 420 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 598319faca1..526e0e129c2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,12 +1,5 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; -import adaptermanager from 'src/adaptermanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { STATUS } from 'src/constants'; -import { userSync } from 'src/userSync'; -const RUBICON_BIDDER_CODE = 'rubicon'; +import { registerBidder } from 'src/adapters/bidderFactory'; // use deferred function call since version isn't defined yet at this point function getIntegration() { @@ -20,6 +13,7 @@ function isSecure() { // use protocol relative urls for http or https const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json'; const VIDEO_ENDPOINT = '//fastlane-adv.rubiconproject.com/v1/auction/video'; +const SYNC_ENDPOINT = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; const TIMEOUT_BUFFER = 500; @@ -72,256 +66,176 @@ var sizeMap = { }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); -function RubiconAdapter() { - var baseAdapter = new Adapter(RUBICON_BIDDER_CODE); - var hasUserSyncFired = false; - - function _callBids(bidderRequest) { - var bids = bidderRequest.bids || []; - - bids.forEach(bid => { - try { - // Video endpoint only accepts POST calls - if (bid.mediaType === 'video') { - ajax( - VIDEO_ENDPOINT, - { - success: bidCallback, - error: bidError - }, - buildVideoRequestPayload(bid, bidderRequest), - { - withCredentials: true - } - ); - } else { - ajax( - buildOptimizedCall(bid), - { - success: bidCallback, - error: bidError - }, - undefined, - { - withCredentials: true - } - ); - } - } catch (err) { - utils.logError('Error sending rubicon request for placement code ' + bid.placementCode, null, err); - addErrorBid(); - } - - function bidCallback(responseText) { - try { - utils.logMessage('XHR callback function called for ad ID: ' + bid.bidId); - handleRpCB(responseText, bid); - } catch (err) { - if (typeof err === 'string') { - utils.logWarn(`${err} when processing rubicon response for placement code ${bid.placementCode}`); - } else { - utils.logError('Error processing rubicon response for placement code ' + bid.placementCode, null, err); - } - addErrorBid(); - } - } - - function bidError(err, xhr) { - utils.logError('Request for rubicon responded with:', xhr.status, err); - addErrorBid(); - } - - function addErrorBid() { - let badBid = bidfactory.createBid(STATUS.NO_BID, bid); - badBid.bidderCode = baseAdapter.getBidderCode(); - bidmanager.addBidResponse(bid.placementCode, badBid); - } - }); - } - - function _getScreenResolution() { - return [window.screen.width, window.screen.height].join('x'); - } - - function _getDigiTrustQueryParams() { - function getDigiTrustId() { - let digiTrustUser = window.DigiTrust && ($$PREBID_GLOBAL$$.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; +export const spec = { + code: 'rubicon', + aliases: ['rubiconLite'], + supportedMediaTypes: ['video'], + /** + * @param {object} bid + * @return boolean + */ + isBidRequestValid: function(bid) { + if (typeof bid.params !== 'object') { + return false; } - let digiTrustId = getDigiTrustId(); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return []; - } - return [ - 'dt.id', digiTrustId.id, - 'dt.keyv', digiTrustId.keyv, - 'dt.pref', 0 - ]; - } - - function buildVideoRequestPayload(bid, bidderRequest) { - bid.startTime = new Date().getTime(); - let params = bid.params; - if (!params || typeof params.video !== 'object') { - throw 'Invalid Video Bid'; + if (!/^\d+$/.test(params.accountId)) { + return false; } - 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: getIntegration(), - timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), - stash_creatives: true, - ae_pass_through_parameters: params.video.aeParams, - slots: [] - }; - - // Define the slot object - let slotData = { - site_id: params.siteId, - zone_id: params.zoneId, - position: params.position || 'btf', - floor: parseFloat(params.floor) > 0.01 ? params.floor : 0.01, - element_id: bid.placementCode, - name: bid.placementCode, - language: params.video.language, - width: size[0], - height: size[1] - }; - - // check and add inventory, keywords, visitor and size_id data - if (params.video.size_id) { - slotData.size_id = params.video.size_id; - } else { - throw 'Invalid Video Bid - Invalid Ad Type!'; - } - - if (params.inventory && typeof params.inventory === 'object') { - slotData.inventory = params.inventory; - } - - if (params.keywords && Array.isArray(params.keywords)) { - slotData.keywords = params.keywords; + let parsedSizes = parseSizes(bid); + if (parsedSizes.length < 1) { + return false; } - if (params.visitor && typeof params.visitor === 'object') { - slotData.visitor = params.visitor; + if (bid.mediaType === 'video') { + if (typeof params.video !== 'object' || !params.video.size_id) { + return false; + } } + return true; + }, + /** + * @param {BidRequest[]} bidRequests + * @param bidderRequest + * @return ServerRequest[] + */ + buildRequests: function(bidRequests, bidderRequest) { + return bidRequests.map(bidRequest => { + bidRequest.startTime = new Date().getTime(); - postData.slots.push(slotData); - - return (JSON.stringify(postData)); - } - - function buildOptimizedCall(bid) { - bid.startTime = new Date().getTime(); - - var { - accountId, - siteId, - zoneId, - position, - floor, - keywords, - visitor, - inventory, - userId, - referrer: pageUrl - } = bid.params; - - // defaults - floor = (floor = parseFloat(floor)) > 0.01 ? floor : 0.01; - position = position || 'btf'; - - // use rubicon sizes if provided, otherwise adUnit.sizes - var parsedSizes = RubiconAdapter.masSizeOrdering(Array.isArray(bid.params.sizes) - ? bid.params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes - ); - - if (parsedSizes.length < 1) { - throw 'no valid sizes'; - } + if (bidRequest.mediaType === 'video') { + let params = bidRequest.params; + let size = parseSizes(bidRequest); + + let data = { + page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, + resolution: _getScreenResolution(), + account_id: params.accountId, + integration: getIntegration(), + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), + stash_creatives: true, + ae_pass_through_parameters: params.video.aeParams, + slots: [] + }; + + // Define the slot object + let slotData = { + site_id: params.siteId, + 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, + language: params.video.language, + width: size[0], + height: size[1], + size_id: params.video.size_id + }; + + if (params.inventory && typeof params.inventory === 'object') { + slotData.inventory = params.inventory; + } - if (!/^\d+$/.test(accountId)) { - throw 'invalid accountId provided'; - } + if (params.keywords && Array.isArray(params.keywords)) { + slotData.keywords = params.keywords; + } - // using array to honor ordering. if order isn't important (it shouldn't be), an object would probably be preferable - var queryString = [ - 'account_id', accountId, - 'site_id', siteId, - 'zone_id', zoneId, - 'size_id', parsedSizes[0], - 'alt_size_ids', parsedSizes.slice(1).join(',') || undefined, - 'p_pos', position, - 'rp_floor', floor, - 'rp_secure', isSecure() ? '1' : '0', - 'tk_flint', getIntegration(), - 'p_screen_res', _getScreenResolution(), - 'kw', keywords, - 'tk_user_key', userId - ]; - - if (visitor !== null && typeof visitor === 'object') { - utils._each(visitor, (item, key) => queryString.push(`tg_v.${key}`, item)); - } + if (params.visitor && typeof params.visitor === 'object') { + slotData.visitor = params.visitor; + } - if (inventory !== null && typeof inventory === 'object') { - utils._each(inventory, (item, key) => queryString.push(`tg_i.${key}`, item)); - } + data.slots.push(slotData); - queryString.push( - 'rand', Math.random(), - 'rf', !pageUrl ? utils.getTopWindowUrl() : pageUrl - ); + return { + method: 'POST', + url: VIDEO_ENDPOINT, + data, + bidRequest + } + } - queryString = queryString.concat(_getDigiTrustQueryParams()); + // non-video request builder + let { + accountId, + siteId, + zoneId, + position, + floor, + keywords, + visitor, + inventory, + userId, + referrer: pageUrl + } = bidRequest.params; + + // defaults + floor = (floor = parseFloat(floor)) > 0.01 ? floor : 0.01; + position = position || 'btf'; + + // use rubicon sizes if provided, otherwise adUnit.sizes + let parsedSizes = parseSizes(bidRequest); + + // using array to honor ordering. if order isn't important (it shouldn't be), an object would probably be preferable + let data = [ + 'account_id', accountId, + 'site_id', siteId, + 'zone_id', zoneId, + 'size_id', parsedSizes[0], + 'alt_size_ids', parsedSizes.slice(1).join(',') || undefined, + 'p_pos', position, + 'rp_floor', floor, + 'rp_secure', isSecure() ? '1' : '0', + 'tk_flint', getIntegration(), + 'tid', bidRequest.transactionId, + 'p_screen_res', _getScreenResolution(), + 'kw', keywords, + 'tk_user_key', userId + ]; - return queryString.reduce( - (memo, curr, index) => - index % 2 === 0 && queryString[index + 1] !== undefined - ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, - FASTLANE_ENDPOINT + '?' - ).slice(0, -1); // remove trailing & - } + if (visitor !== null && typeof visitor === 'object') { + utils._each(visitor, (item, key) => data.push(`tg_v.${key}`, item)); + } - let _renderCreative = (script, impId) => ` - - - -
- -
- -`; + if (inventory !== null && typeof inventory === 'object') { + utils._each(inventory, (item, key) => data.push(`tg_i.${key}`, item)); + } - function handleRpCB(responseText, bidRequest) { - const responseObj = JSON.parse(responseText); // can throw + data.push( + 'rand', Math.random(), + 'rf', !pageUrl ? utils.getTopWindowUrl() : pageUrl + ); + + data = data.concat(_getDigiTrustQueryParams()); + + data = data.reduce( + (memo, curr, index) => + index % 2 === 0 && data[index + 1] !== undefined + ? memo + curr + '=' + encodeURIComponent(data[index + 1]) + '&' : memo, + '' + ).slice(0, -1); // remove trailing & + + return { + method: 'GET', + url: FASTLANE_ENDPOINT, + data, + bidRequest + }; + }); + }, + /** + * @param {*} responseObj + * @param {bidRequest} bidRequest + * @return {Bid[]} An array of bids which + */ + interpretResponse: function(responseObj, {bidRequest}) { let ads = responseObj.ads; const adResponseKey = bidRequest.placementCode; // check overall response if (typeof responseObj !== 'object' || responseObj.status !== 'ok') { - throw 'bad response'; + return []; } // video ads array is wrapped in an object @@ -331,25 +245,25 @@ function RubiconAdapter() { // check the ad response if (!Array.isArray(ads) || ads.length < 1) { - throw 'invalid ad response'; + return []; } // if there are multiple ads, sort by CPM ads = ads.sort(_adCpmSort); - ads.forEach(ad => { + let bids = ads.reduce((bids, ad) => { if (ad.status !== 'ok') { - throw 'bad ad status'; + return; } - // store bid response - // bid status is good (indicating 1) - var bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - bid.currency = 'USD'; - bid.creative_id = ad.creative_id; - bid.bidderCode = baseAdapter.getBidderCode(); - bid.cpm = ad.cpm || 0; - bid.dealId = ad.deal; + let bid = { + requestId: bidRequest.bidId, + currency: 'USD', + creative_id: ad.creative_id, + bidderCode: spec.code, + cpm: ad.cpm || 0, + dealId: ad.deal + }; if (bidRequest.mediaType === 'video') { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; @@ -368,26 +282,84 @@ function RubiconAdapter() { return memo; }, {'rpfl_elemid': bidRequest.placementCode}); - try { - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } catch (err) { - utils.logError('Error from addBidResponse', null, err); - } - }); - // Run the Emily user sync - hasUserSyncFired = syncEmily(hasUserSyncFired); + bids.push(bid); + + return bids; + }, []); + + return bids; + }, + getUserSyncs: function() { + if (!hasSynced) { + hasSynced = true; + return { + type: 'iframe', + url: SYNC_ENDPOINT + }; + } } +}; + +function _adCpmSort(adA, adB) { + return (adB.cpm || 0.0) - (adA.cpm || 0.0); +} - function _adCpmSort(adA, adB) { - return (adB.cpm || 0.0) - (adA.cpm || 0.0); +function _getScreenResolution() { + return [window.screen.width, window.screen.height].join('x'); +} + +function _getDigiTrustQueryParams() { + function getDigiTrustId() { + let digiTrustUser = window.DigiTrust && ($$PREBID_GLOBAL$$.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; } + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return []; + } + return [ + 'dt.id', digiTrustId.id, + 'dt.keyv', digiTrustId.keyv, + 'dt.pref', 0 + ]; +} - return Object.assign(this, baseAdapter, { - callBids: _callBids - }); +function _renderCreative(script, impId) { + return ` + + + +
+ +
+ +`; } -RubiconAdapter.masSizeOrdering = function(sizes) { +function parseSizes(bid) { + let params = bid.params; + if (bid.mediaType === 'video') { + 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]; + } + return size; + } + return masSizeOrdering(Array.isArray(params.sizes) + ? params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes + ); +} + +export function masSizeOrdering(sizes) { const MAS_SIZE_PRIORITY = [15, 2, 9]; return utils.parseSizesInput(sizes) @@ -417,41 +389,11 @@ RubiconAdapter.masSizeOrdering = function(sizes) { // and finally ascending order return first - second; }); -}; - -/** - * syncEmily - * @summary A user sync dependency for the Rubicon Project adapter - * Registers an Emily iframe user sync to be called/created later by Prebid - * Only registers once except that with each winning creative there will be additional, similar calls to the same service. Must enable iframe syncs which are off by default -@example - * // Config example for iframe user sync - * $$PREBID_GLOBAL$$.setConfig({ userSync: { - * syncEnabled: true, - * pixelEnabled: true, - * syncsPerBidder: 5, - * syncDelay: 3000, - * iframeEnabled: true - * }}); - * @return {boolean} Whether or not Emily synced - */ -function syncEmily(hasSynced) { - // Check that it has not already been triggered - only meant to fire once - if (hasSynced) { - return true; - } - - const iframeUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; - - // register the sync with the Prebid (to be called later) - userSync.registerSync('iframe', 'rubicon', iframeUrl); - - return true; } -adaptermanager.registerBidAdapter(new RubiconAdapter(), RUBICON_BIDDER_CODE, { - supportedMediaTypes: ['video'] -}); -adaptermanager.aliasBidAdapter(RUBICON_BIDDER_CODE, 'rubiconLite'); +var hasSynced = false; +export function resetUserSync() { + hasSynced = false; +} -module.exports = RubiconAdapter; +registerBidder(spec); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 6b5a0090ac3..641af863f15 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -24,7 +24,7 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * aliases: ['alias1', 'alias2'], * supportedMediaTypes: ['video', 'native'], * isBidRequestValid: function(paramsObject) { return true/false }, - * buildRequests: function(bidRequests) { return some ServerRequest(s) }, + * buildRequests: function(bidRequests, bidderRequest) { return some ServerRequest(s) }, * interpretResponse: function(oneServerResponse) { return some Bids, or throw an error. } * }); * @@ -41,7 +41,7 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {MediaType[]} [supportedMediaTypes]: A list of Media Types which the adapter supports. * @property {function(object): boolean} isBidRequestValid Determines whether or not the given bid has all the params * needed to make a valid request. - * @property {function(BidRequest[]): ServerRequest|ServerRequest[]} buildRequests Build the request to the Server + * @property {function(BidRequest[], bidderRequest): ServerRequest|ServerRequest[]} buildRequests Build the request to the Server * which requests Bids for the given array of Requests. Each BidRequest in the argument array is guaranteed to have * passed the isBidRequestValid() test. * @property {function(*, BidRequest): Bid[]} interpretResponse Given a successful response from the Server, @@ -79,6 +79,8 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {string} ad A URL which can be used to load this ad, if it's chosen by the publisher. * @property {string} currency The currency code for the cpm value * @property {number} cpm The bid price, in US cents per thousand impressions. + * @property {number} ttl Time-to-live - how long (in seconds) Prebid can use this bid. + * @property {boolean} netRevenue Boolean defining whether the bid is Net or Gross. The default is true (Net). * @property {number} height The height of the ad, in pixels. * @property {number} width The width of the ad, in pixels. * @@ -152,18 +154,26 @@ export function newBidder(spec) { const adUnitCodesHandled = {}; function addBidWithCode(adUnitCode, bid) { adUnitCodesHandled[adUnitCode] = true; - bidmanager.addBidResponse(adUnitCode, bid); + addBid(adUnitCode, bid); } function fillNoBids() { bidderRequest.bids .map(bidRequest => bidRequest.placementCode) .forEach(adUnitCode => { if (adUnitCode && !adUnitCodesHandled[adUnitCode]) { - bidmanager.addBidResponse(adUnitCode, newEmptyBid()); + addBid(adUnitCode, newEmptyBid()); } }); } + function addBid(code, bid) { + try { + bidmanager.addBidResponse(code, bid); + } catch (err) { + logError('Error adding bid', code, err); + } + } + // After all the responses have come back, fill up the "no bid" bids and // register any required usersync pixels. const responses = []; @@ -185,17 +195,17 @@ export function newBidder(spec) { } } - const bidRequests = bidderRequest.bids.filter(filterAndWarn); - if (bidRequests.length === 0) { + const validBidRequests = bidderRequest.bids.filter(filterAndWarn); + if (validBidRequests.length === 0) { afterAllResponses(); return; } const bidRequestMap = {}; - bidRequests.forEach(bid => { + validBidRequests.forEach(bid => { bidRequestMap[bid.bidId] = bid; }); - let requests = spec.buildRequests(bidRequests, bidderRequest); + let requests = spec.buildRequests(validBidRequests, bidderRequest); if (!requests || requests.length === 0) { afterAllResponses(); return; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 183b79f7ec9..5573de34f27 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; import adapterManager from 'src/adaptermanager'; import bidManager from 'src/bidmanager'; -import RubiconAdapter from 'modules/rubiconBidAdapter'; +import { spec, masSizeOrdering, resetUserSync } from 'modules/rubiconBidAdapter'; import { parse as parseQuery } from 'querystring'; +import { newBidder } from 'src/adapters/bidderFactory'; import { userSync } from 'src/userSync'; var CONSTANTS = require('src/constants.json'); @@ -55,6 +56,8 @@ describe('the rubicon adapter', () => { beforeEach(() => { sandbox = sinon.sandbox.create(); + sandbox.useFakeServer(); + adUnit = { code: '/19968336/header-bid-tag-0', sizes: [[300, 250], [320, 50]], @@ -111,7 +114,8 @@ describe('the rubicon adapter', () => { sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', - requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a' + requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } ], start: 1472239426002, @@ -162,8 +166,6 @@ describe('the rubicon adapter', () => { }); describe('MAS mapping / ordering', () => { - let masSizeOrdering = RubiconAdapter.masSizeOrdering; - it('should not include values without a proper mapping', () => { // two invalid sizes included: [42, 42], [1, 1] let ordering = masSizeOrdering([[320, 50], [42, 42], [300, 250], [640, 480], [1, 1], [336, 280]]); @@ -192,31 +194,29 @@ describe('the rubicon adapter', () => { describe('callBids implementation', () => { let rubiconAdapter; - describe('for requests', () => { - let xhr, - bids; - - beforeEach(() => { - rubiconAdapter = new RubiconAdapter(); - - bids = []; + let bids, + addBidResponseAction; - xhr = sandbox.useFakeXMLHttpRequest(); + beforeEach(() => { + rubiconAdapter = newBidder(spec); - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - }); - }); + bids = []; - afterEach(() => { - xhr.restore(); + sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { + bids.push(bid); + if (typeof addBidResponseAction === 'function') { + addBidResponseAction(); + addBidResponseAction = undefined; + } }); + }); + describe('for requests', () => { describe('to fastlane', () => { it('should make a well-formed request', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let [path, query] = request.url.split('?'); query = parseQuery(query); @@ -235,6 +235,7 @@ describe('the rubicon adapter', () => { 'rp_floor': '0.01', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, + 'tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -264,7 +265,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(sizesBidderRequest); - let query = parseQuery(xhr.requests[0].url.split('?')[1]); + let query = parseQuery(sandbox.server.requests[0].url.split('?')[1]); expect(query['size_id']).to.equal('55'); expect(query['alt_size_ids']).to.equal('57,59'); @@ -276,7 +277,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(sizesBidderRequest); - expect(xhr.requests.length).to.equal(0); + expect(sandbox.server.requests.length).to.equal(0); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -289,7 +290,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(noAccountBidderRequest); - expect(xhr.requests.length).to.equal(0); + expect(sandbox.server.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); @@ -301,7 +302,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(floorBidderRequest); - let query = parseQuery(xhr.requests[0].url.split('?')[1]); + let query = parseQuery(sandbox.server.requests[0].url.split('?')[1]); expect(query['rp_floor']).to.equal('2'); }); @@ -323,7 +324,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -346,7 +347,7 @@ describe('the rubicon adapter', () => { it('should not send digitrust params when DigiTrust not loaded', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -376,7 +377,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -408,7 +409,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -427,7 +428,7 @@ describe('the rubicon adapter', () => { var origGetConfig; beforeEach(() => { window.DigiTrust = { - getUser: sinon.spy() + getUser: sandbox.spy() }; origGetConfig = window.$$PREBID_GLOBAL$$.getConfig; }); @@ -438,7 +439,7 @@ describe('the rubicon adapter', () => { }); it('should send digiTrustId config params', () => { - sinon.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { var config = { digiTrustId: { success: true, @@ -454,7 +455,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -475,7 +476,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to optout', () => { - sinon.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { var config = { digiTrustId: { success: true, @@ -491,7 +492,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -508,7 +509,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to failure', () => { - sinon.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { var config = { digiTrustId: { success: false, @@ -524,7 +525,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -541,14 +542,14 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params if they do not exist', () => { - sinon.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { var config = {}; return config[key]; }); rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let query = request.url.split('?')[1]; query = parseQuery(query); @@ -567,16 +568,6 @@ describe('the rubicon adapter', () => { }); describe('for video requests', () => { - /* - beforeEach(() => { - createVideoBidderRequest(); - - sandbox.stub(Date, 'now', () => - bidderRequest.auctionStart + 100 - ); - }); - */ - it('should make a well-formed video request', () => { createVideoBidderRequest(); @@ -586,7 +577,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let url = request.url; let post = JSON.parse(request.requestBody); @@ -653,7 +644,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(floorBidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let post = JSON.parse(request.requestBody); let floor = post.slots[0].floor; @@ -671,7 +662,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(floorBidderRequest); - expect(xhr.requests.length).to.equal(0); + expect(sandbox.server.requests.length).to.equal(0); }); it('should get size from bid.sizes too', () => { @@ -684,7 +675,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(floorBidderRequest); - let request = xhr.requests[0]; + let request = sandbox.server.requests[0]; let post = JSON.parse(request.requestBody); expect(post.slots[0].width).to.equal(300); @@ -694,31 +685,9 @@ describe('the rubicon adapter', () => { }); describe('response handler', () => { - let bids, - server, - addBidResponseAction; - - beforeEach(() => { - bids = []; - - server = sinon.fakeServer.create(); - - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - if (addBidResponseAction) { - addBidResponseAction(); - addBidResponseAction = undefined; - } - }); - }); - - afterEach(() => { - server.restore(); - }); - describe('for fastlane', () => { it('should handle a success response and sort by cpm', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -777,7 +746,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledTwice).to.equal(true); @@ -809,7 +778,7 @@ describe('the rubicon adapter', () => { }); it('should be fine with a CPM of 0', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -829,7 +798,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -837,7 +806,7 @@ describe('the rubicon adapter', () => { }); it('should return currency "USD"', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -857,7 +826,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -866,7 +835,7 @@ describe('the rubicon adapter', () => { }); it('should handle an error with no ads returned', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -882,7 +851,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -890,7 +859,7 @@ describe('the rubicon adapter', () => { }); it('should handle an error with bad status', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -908,7 +877,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -916,11 +885,11 @@ describe('the rubicon adapter', () => { }); it('should handle an error because of malformed json response', () => { - server.respondWith('{test{'); + sandbox.server.respondWith('{test{'); rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -928,11 +897,11 @@ describe('the rubicon adapter', () => { }); it('should handle error contacting endpoint', () => { - server.respondWith([404, {}, '']); + sandbox.server.respondWith([404, {}, '']); rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(bidManager.addBidResponse.calledOnce).to.equal(true); expect(bids).to.be.lengthOf(1); @@ -940,7 +909,7 @@ describe('the rubicon adapter', () => { }); it('should not register an error bid when a success call to addBidResponse throws an error', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -964,7 +933,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); // was calling twice for same bid, but should only call once expect(bidManager.addBidResponse.calledOnce).to.equal(true); @@ -978,7 +947,7 @@ describe('the rubicon adapter', () => { }); it('should register a successful bid', () => { - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'ads': { '/19968336/header-bid-tag-0': [ @@ -1007,7 +976,7 @@ describe('the rubicon adapter', () => { rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); // was calling twice for same bid, but should only call once expect(bidManager.addBidResponse.calledOnce).to.equal(true); @@ -1029,29 +998,16 @@ describe('the rubicon adapter', () => { }); describe('user sync', () => { - let bids; - let server; - let addBidResponseAction; let rubiconAdapter; let userSyncStub; const emilyUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; beforeEach(() => { - bids = []; - - server = sinon.fakeServer.create(); // monitor userSync registrations - userSyncStub = sinon.stub(userSync, 'registerSync'); + userSyncStub = sandbox.stub(userSync, 'registerSync'); + sandbox.stub(bidManager, 'addBidResponse'); - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - if (addBidResponseAction) { - addBidResponseAction(); - addBidResponseAction = undefined; - } - }); - - server.respondWith(JSON.stringify({ + sandbox.server.respondWith(JSON.stringify({ 'status': 'ok', 'account_id': 14062, 'site_id': 70608, @@ -1093,18 +1049,14 @@ describe('the rubicon adapter', () => { iframes[i].outerHTML = ''; } - rubiconAdapter = new RubiconAdapter(); - }); - - afterEach(() => { - server.restore(); - userSyncStub.restore(); + rubiconAdapter = newBidder(spec); + resetUserSync(); }); it('should register the Emily iframe', () => { expect(userSyncStub.calledOnce).to.be.false; rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(userSyncStub.calledOnce).to.be.true; expect(userSyncStub.getCall(0).args).to.eql(['iframe', 'rubicon', emilyUrl]); }); @@ -1112,11 +1064,11 @@ describe('the rubicon adapter', () => { it('should not register the Emily iframe more than once', () => { expect(userSyncStub.calledOnce).to.be.false; rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(userSyncStub.calledOnce).to.be.true; // run another auction, should still have only been called once rubiconAdapter.callBids(bidderRequest); - server.respond(); + sandbox.server.respond(); expect(userSyncStub.calledOnce).to.be.true; }); });