diff --git a/src/prebid.js b/src/prebid.js index 7f1e5b32acb..fcec8e5ad83 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -25,7 +25,6 @@ var BID_WON = CONSTANTS.EVENTS.BID_WON; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; var pb_bidsTimedOut = false; -var pb_sendAllBids = false; var auctionRunning = false; var presetTargeting = []; @@ -38,6 +37,7 @@ var eventValidators = { pbjs._bidsRequested = []; pbjs._bidsReceived = []; pbjs._adsReceived = []; +pbjs._sendAllBids = false; //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; @@ -177,7 +177,7 @@ function getWinningBidTargeting() { } function getDealTargeting() { - const dealTargeting = pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { + return pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { const dealKey = `hb_deal_${bid.bidderCode}`; return { [bid.adUnitCode]: CONSTANTS.TARGETING_KEYS.map(key => { @@ -188,8 +188,29 @@ function getDealTargeting() { .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) }; }); +} - return dealTargeting; +/** + * Get custom targeting keys for bids that have `alwaysUseBid=true`. + */ +function getAlwaysUseBidTargeting() { + return pbjs._bidsReceived.map(bid => { + if (bid.alwaysUseBid) { + const standardKeys = CONSTANTS.TARGETING_KEYS; + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting, key => key).map(key => { + // Get only the non-standard keys of the losing bids, since we + // don't want to override the standard keys of the winning bid. + if (standardKeys.indexOf(key) > -1) { + return; + } + + return { [key.substring(0, 20)]: [bid.adserverTargeting[key]] }; + + }).filter(key => key) // remove empty elements + }; + } + }).filter(bid => bid); // removes empty elements in array; } function getBidLandscapeTargeting() { @@ -209,10 +230,12 @@ function getBidLandscapeTargeting() { } function getAllTargeting() { - let targeting = getWinningBidTargeting(); - // deals are always attached to targeting - targeting = getDealTargeting().concat(targeting); - return targeting.concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); + // Get targeting for the winning bid. Add targeting for any bids that have + // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. + return getDealTargeting() + .concat(getWinningBidTargeting()) + .concat(getAlwaysUseBidTargeting()) + .concat(pbjs._sendAllBids ? getBidLandscapeTargeting() : []); } ////////////////////////////////// @@ -240,7 +263,7 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { }; /** -* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for * @returns {object} returnObj return bids */ @@ -423,7 +446,7 @@ pbjs.removeAdUnit = function (adUnitCode) { } }; -pbjs.clearAuction = function() { +pbjs.clearAuction = function () { auctionRunning = false; utils.logMessage('Prebid auction cleared'); }; @@ -676,7 +699,7 @@ pbjs.setPriceGranularity = function (granularity) { }; pbjs.enableSendAllBids = function () { - pb_sendAllBids = true; + pbjs._sendAllBids = true; }; processQue(); diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 3799eab7208..5b6c95b20a7 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1053,6 +1053,7 @@ export function getAdUnits() { ] }; +// Ad server targeting when `pbjs.enableSendAllBids()` is called. export function getAdServerTargeting() { return { "/19968336/header-bid-tag-0": { @@ -1103,3 +1104,176 @@ export function getAdServerTargeting() { } }; } + +// Key/values used to set ad server targeting. +export function getTargetingKeys() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ] + ]; +} + +// Key/values used to set ad server targeting when bid landscape +// targeting is on. +export function getTargetingKeysBidLandscape() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "hb_bidder_triplelift", + "triplelift" + ], + [ + "hb_adid_triplelift", + "222bb26f9e8bd" + ], + [ + "hb_pb_triplelift", + "10.00" + ], + [ + "hb_size_triplelift", + "0x0" + ], + [ + "hb_bidder_appnexus", + "appnexus" + ], + [ + "hb_adid_appnexus", + "233bcbee889d46d" + ], + [ + "hb_pb_appnexus", + "10.00" + ], + [ + "hb_size_appnexus", + "300x250" + ], + [ + "hb_bidder_pagescienc", + "pagescience" + ], + [ + "hb_adid_pagescience", + "25bedd4813632d7" + ], + [ + "hb_pb_pagescience", + "10.00" + ], + [ + "hb_size_pagescience", + "300x250" + ], + [ + "hb_bidder_brightcom", + "brightcom" + ], + [ + "hb_adid_brightcom", + "26e0795ab963896" + ], + [ + "hb_pb_brightcom", + "10.00" + ], + [ + "hb_size_brightcom", + "300x250" + ], + [ + "hb_bidder_brealtime", + "brealtime" + ], + [ + "hb_adid_brealtime", + "275bd666f5a5a5d" + ], + [ + "hb_pb_brealtime", + "10.00" + ], + [ + "hb_size_brealtime", + "300x250" + ], + [ + "hb_bidder_pubmatic", + "pubmatic" + ], + [ + "hb_adid_pubmatic", + "28f4039c636b6a7" + ], + [ + "hb_pb_pubmatic", + "10.00" + ], + [ + "hb_size_pubmatic", + "300x250" + ], + [ + "hb_bidder_rubicon", + "rubicon" + ], + [ + "hb_adid_rubicon", + "29019e2ab586a5a" + ], + [ + "hb_pb_rubicon", + "10.00" + ], + [ + "hb_size_rubicon", + "300x600" + ] + ]; +} diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 68ecc092200..816ea43924c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1,4 +1,10 @@ -import { getBidRequests, getBidResponses, getAdServerTargeting } from 'test/fixtures/fixtures'; +import { + getAdServerTargeting, + getBidRequests, + getBidResponses, + getTargetingKeys, + getTargetingKeysBidLandscape, +} from 'test/fixtures/fixtures'; var assert = require('chai').assert; @@ -20,6 +26,7 @@ pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); function resetAuction() { + pbjs._sendAllBids = false; pbjs.clearAuction(); pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); @@ -110,12 +117,90 @@ describe('Unit: Prebid Module', function () { }); describe('getAdServerTargeting', function () { + + beforeEach(() => { + resetAuction(); + }); + + afterEach(() => { + resetAuction(); + }); + it('should return current targeting data for slots', function () { + pbjs.enableSendAllBids(); const targeting = pbjs.getAdserverTargeting(); const expected = getAdServerTargeting(); - pbjs.enableSendAllBids(); assert.deepEqual(targeting, expected, 'targeting ok'); }); + + it('should return correct targeting with default settings', () => { + var targeting = pbjs.getAdserverTargeting(); + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + assert.deepEqual(targeting, expected); + }); + + it('should return correct targeting with bid landscape targeting on', () => { + pbjs.enableSendAllBids(); + var targeting = pbjs.getAdserverTargeting(); + var expected = getAdServerTargeting(); + assert.deepEqual(targeting, expected); + }); + + it("should include a losing bid's custom ad targeting key when the bid has `alwaysUseBid` set to `true`", () => { + + // Let's make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var targeting = pbjs.getAdserverTargeting(); + + // Ensure targeting for both ad placements includes the custom key. + assert.equal( + targeting['/19968336/header-bid-tag-0'].hasOwnProperty('always_use_me'), + true + ); + + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus", + "always_use_me": "abc" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + + assert.deepEqual(targeting, expected); + }); }); describe('getBidResponses', function () { @@ -147,8 +232,16 @@ describe('Unit: Prebid Module', function () { describe('setTargetingForGPTAsync', function () { let logErrorSpy; - beforeEach(() => logErrorSpy = sinon.spy(utils, 'logError')); - afterEach(() => utils.logError.restore()); + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + resetAuction(); + }); + + afterEach(() => { + utils.logError.restore(); + resetAuction(); + }); it('should set targeting when passed an array of ad unit codes', function () { var slots = createSlotArray(); @@ -163,6 +256,9 @@ describe('Unit: Prebid Module', function () { window.googletag.pubads().setSlots(slots); pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeys(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); }); it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + @@ -172,6 +268,60 @@ describe('Unit: Prebid Module', function () { pbjs.enableSendAllBids(); pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeysBidLandscape(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); + }); + + it('should set targeting for bids with `alwaysUseBid=true`', function () { + + // Make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + + var expected = [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "always_use_me", + "abc" + ], + [ + "foobar", + "300x250" + ] + ]; + + assert.deepEqual(slots[0].spySetTargeting.args, expected); }); it('should log error when googletag is not defined on page', function () {