diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index c80f1560660..6d99705a9bd 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -63,11 +63,11 @@ var RubiconAdapter = function RubiconAdapter() { /** * Create an error bid * @param {String} placement - the adunit path - * @param {Object} response - the (error) response from fastlane + * @param {Object} slot - the (error) response from fastlane * @return {Bid} a bid, for prebid */ - function _errorBid(response, ads) { - var bidResponse = bidfactory.createBid(2, response.bid); + function _errorBid(slot, ads) { + var bidResponse = bidfactory.createBid(2, slot.bid); bidResponse.bidderCode = RUBICON_BIDDER_CODE; // use the raw ads as the 'error' @@ -107,70 +107,68 @@ var RubiconAdapter = function RubiconAdapter() { } /** - * Create (successful) bids for a unit, + * Create (successful) bids for a slot, * based on the given response - * @param {String} placement placement code/unit path - * @param {Object} response the response from rubicon - * @return {Bid} a bid objectj + * @param {Object} slot the slot from rubicon + * @param {Object} ads the raw responses */ - function _makeBids(response, ads) { + function _makeBids(slot, ads) { - // if there are multiple ads, sort by CPM - ads = ads.sort(_adCpmSort); - - var bidResponses = []; - - ads.forEach(function(ad) { - - var bidResponse, - size = ad.dimensions; - - if (!size) { - // this really shouldn't happen - utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); - bidResponse = _errorBid(response, ads); - } else { - bidResponse = bidfactory.createBid(1, response.bid); + if (!ads || ads.length === 0) { - bidResponse.bidderCode = RUBICON_BIDDER_CODE; - bidResponse.cpm = ad.cpm; + bidmanager.addBidResponse( + slot.getElementId(), + _errorBid(slot, ads) + ); - // the element id is what the iframe will use to render - // itself using the rubicontag.renderCreative API - bidResponse.ad = _creative(response.getElementId(), size); - bidResponse.width = size[0]; - bidResponse.height = size[1]; + } else { - // DealId - if (ad.deal) { - bidResponse.dealId = ad.deal; - } - } + // if there are multiple ads, sort by CPM + ads = ads.sort(_adCpmSort); - bidResponses.push(bidResponse); + ads.forEach(function (ad) { + _makeBid(slot, ad); + }); - }); + } - return bidResponses; } /** - * Add success/error bids based - * on the response from rubicon - * @param {Object} response -- AJAX response from fastlane + * Create (successful) bid for a slot size, + * based on the given response + * @param {Object} slot the slot from rubicon + * @param {Object} ad a raw response */ - function _addBids(response, ads) { - // get the bid for the placement code - var bids; - if (!ads || ads.length === 0) { - bids = [ _errorBid(response, ads) ]; + function _makeBid(slot, ad) { + + var bidResponse, + size = ad.dimensions; + + if (!size) { + // this really shouldn't happen + utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); + bidResponse = _errorBid(slot, ad); } else { - bids = _makeBids(response, ads); + bidResponse = bidfactory.createBid(1, slot.bid); + + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + bidResponse.cpm = ad.cpm; + + // the element id is what the iframe will use to render + // itself using the rubicontag.renderCreative API + bidResponse.ad = _creative(slot.getElementId(), size); + bidResponse.width = size[0]; + bidResponse.height = size[1]; + + // DealId + if (ad.deal) { + bidResponse.dealId = ad.deal; + } } - bids.forEach(function(bid) { - bidmanager.addBidResponse(response.getElementId(), bid); - }); + bidmanager.addBidResponse(slot.getElementId(), bidResponse); + } /** @@ -286,10 +284,32 @@ var RubiconAdapter = function RubiconAdapter() { utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); utils._each(slots, function (slot) { - _addBids(slot, slot.getRawResponses()); + _makeBids(slot, slot.getRawResponses()); }); } + + var _cb; + var _eventAvailable; + /** + * Used to attach (and switch out) callback for listening to rubicon bid events + * Rubicon + * @param {Function} cb Callback to register with event handler + * @return {Boolean} whether we can handle the event or not + */ + function _handleBidEvent(cb) { + _cb = cb; + if(_eventAvailable) { + return true; + } + if (_eventAvailable === false) { + return false; + } + return _eventAvailable = window.rubicontag.addEventListener('FL_TIER_MAPPED', params => { + _cb(params); + }) + } + /** * Request the specified bids from * Rubicon @@ -325,9 +345,23 @@ var RubiconAdapter = function RubiconAdapter() { slots: slots, timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart - TIMEOUT_BUFFER) }; - var callback = function () { - _bidsReady(slots); - }; + var callback = function noop() {}; + + if(!_handleBidEvent(params => { + + var slot = slots.find(slot => slot.getElementId() === params.elementId); + var ad = slot.getRawResponseBySizeId(params.sizeId); + var time = ((new Date).getTime() - _bidStart); + + utils.logMessage(`Rubicon Project bid back for "${params.elementId}" size ${params.sizeId} at: ${time}`); + + _makeBid(slot, ad); + + })) { + callback = () => { + _bidsReady(slots); + } + } window.rubicontag.setIntegration('$$PREBID_GLOBAL$$'); window.rubicontag.run(callback, parameters); diff --git a/test/spec/adapters/rubicon_spec.js b/test/spec/adapters/rubicon_spec.js index ddc40a8b5e7..094409f1d21 100644 --- a/test/spec/adapters/rubicon_spec.js +++ b/test/spec/adapters/rubicon_spec.js @@ -2,6 +2,7 @@ import { expect } from "chai"; import adloader from "src/adloader"; import adapterManager from "src/adaptermanager"; import bidManager from "src/bidmanager"; +import RubiconAdapter from "src/adapters/rubicon"; var CONSTANTS = require("src/constants.json"); @@ -99,7 +100,8 @@ describe("the rubicon adapter", () => { addFPI: sandbox.spy(), addKW: sandbox.spy(), getElementId: () => "/19968336/header-bid-tag-0", - getRawResponses: () => {} + getRawResponses: () => {}, + getRawResponseBySizeId: () => {} }; window.rubicontag = { @@ -108,6 +110,7 @@ describe("the rubicon adapter", () => { }, setIntegration: sandbox.spy(), run: () => {}, + addEventListener: () => {}, setUserKey: sandbox.spy(), defineSlot: sandbox.spy(bid => slot) }; @@ -216,68 +219,165 @@ describe("the rubicon adapter", () => { describe("when handling fastlane responses", () => { - let bids; - beforeEach(() => { - bids = []; + // need a fresh rubicon adapter for these tests to reset private state. + rubiconAdapter = new RubiconAdapter(); + }); + + describe("individually through events", () => { + + let bids; + let _callback; + let addEventListener; - sinon.stub(window.rubicontag, "run", cb => cb()); - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); + beforeEach(() => { + bids = []; + + addEventListener = sandbox.stub(window.rubicontag, "addEventListener", (event, callback) => { + _callback = callback; + return true; + }); + + sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { + bids.push(bid); + }); }); + + it("should only register one listener for multiple bid requests", () => { + + rubiconAdapter.callBids(bidderRequest); + rubiconAdapter.callBids(bidderRequest); + + expect(addEventListener.calledOnce).to.equal(true); + + }); + + it("should register successful bids with the bidmanager", () => { + + sandbox.stub(window.rubicontag, "run", () => { + _callback({ + elementId: "/19968336/header-bid-tag-0", + sizeId: "43" + }); + _callback({ + elementId: "/19968336/header-bid-tag-0", + sizeId: "15" + }); + }); + + sandbox.stub(slot, "getRawResponseBySizeId", (sizeId) => { + return { + "43": { + "advertiser": 12345, + "cpm": 0.811, + "dimensions": [ + 300, + 250 + ], + "auction_id": "431ee1bc-3cc4-4bb7-b0d4-eb9faedb433c" + }, + "15": { + "advertiser": 12345, + "cpm": 0.59, + "dimensions": [ + 320, + 50 + ], + "auction_id": "431ee1bc-3cc4-4bb7-b0d4-eb9faedb433c" + } + }[sizeId]; + }); + + rubiconAdapter.callBids(bidderRequest); + + expect(bidManager.addBidResponse.calledTwice).to.equal(true); + + expect(bids).to.be.lengthOf(2); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + + expect(bids[0].bidderCode).to.equal("rubicon"); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].cpm).to.equal(0.811); + + expect(bids[1].bidderCode).to.equal("rubicon"); + expect(bids[1].width).to.equal(320); + expect(bids[1].height).to.equal(50); + expect(bids[1].cpm).to.equal(0.59); + }) + }); - it("should register successful bids with the bidmanager", () => { + describe("all at once", () => { - sandbox.stub(slot, "getRawResponses", () => [ - { - "advertiser": 12345, - "cpm": 0.811, - "dimensions": [ - 300, - 250 - ], - "auction_id": "431ee1bc-3cc4-4bb7-b0d4-eb9faedb433c" - }, - { - "advertiser": 123456, - "cpm": 0.59, - "dimensions": [ - 320, - 50 - ], - "auction_id": "a3e042e5-3fb7-498f-b60e-71540f4769a8" - } - ]); + let bids; - rubiconAdapter.callBids(bidderRequest); + beforeEach(() => { + bids = []; - expect(bidManager.addBidResponse.calledTwice).to.equal(true); + sandbox.stub(window.rubicontag, "run", cb => cb()); + sandbox.stub(window.rubicontag, "addEventListener", () => false); + sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { + bids.push(bid); + }); + }); - expect(bids).to.be.lengthOf(2); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + it("should register successful bids with the bidmanager", () => { + + sandbox.stub(slot, "getRawResponses", () => [ + { + "advertiser": 12345, + "cpm": 0.811, + "dimensions": [ + 300, + 250 + ], + "auction_id": "431ee1bc-3cc4-4bb7-b0d4-eb9faedb433c" + }, + { + "advertiser": 123456, + "cpm": 0.59, + "dimensions": [ + 320, + 50 + ], + "auction_id": "a3e042e5-3fb7-498f-b60e-71540f4769a8" + } + ]); - expect(bids[0].bidderCode).to.equal("rubicon"); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].cpm).to.equal(0.811); + debugger; - expect(bids[1].bidderCode).to.equal("rubicon"); - expect(bids[1].width).to.equal(320); - expect(bids[1].height).to.equal(50); - expect(bids[1].cpm).to.equal(0.59); + rubiconAdapter.callBids(bidderRequest); - }); + expect(bidManager.addBidResponse.calledTwice).to.equal(true); - it("should register bad responses as errors with the bidmanager", () => { + expect(bids).to.be.lengthOf(2); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bids[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - sandbox.stub(slot, "getRawResponses", () => []); + expect(bids[0].bidderCode).to.equal("rubicon"); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].cpm).to.equal(0.811); - rubiconAdapter.callBids(bidderRequest); + expect(bids[1].bidderCode).to.equal("rubicon"); + expect(bids[1].width).to.equal(320); + expect(bids[1].height).to.equal(50); + expect(bids[1].cpm).to.equal(0.59); - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + }); + + it("should register bad responses as errors with the bidmanager", () => { + + sandbox.stub(slot, "getRawResponses", () => []); + + rubiconAdapter.callBids(bidderRequest); + + expect(bidManager.addBidResponse.calledOnce).to.equal(true); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + + }); });