diff --git a/src/auction.js b/src/auction.js index e28ad953a32..01d0f4cdc36 100644 --- a/src/auction.js +++ b/src/auction.js @@ -364,7 +364,6 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj) { // 2) set keys from specific bidder setting override if they exist if (bidderCode && custBidObj && bidder_settings && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { setKeys(keyValues, bidder_settings[bidderCode], custBidObj); - custBidObj.alwaysUseBid = bidder_settings[bidderCode].alwaysUseBid; custBidObj.sendStandardTargeting = bidder_settings[bidderCode].sendStandardTargeting; } diff --git a/src/auctionManager.js b/src/auctionManager.js index 6ea817d4ef1..36ddec10cb9 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -31,9 +31,6 @@ export function newAuctionManager() { let _auctions = []; let auctionManager = {}; - // return bids whose status is not set. Winning bid can have status `targetingSet` or `rendered`. - const isUnusedBid = (bid) => bid && !bid.status; - auctionManager.addWinningBid = function(bid) { const auction = _auctions.find(auction => auction.getAuctionId() === bid.auctionId); if (auction) { @@ -62,7 +59,7 @@ export function newAuctionManager() { return auction.getBidsReceived(); } }).reduce(flatten, []) - .filter(isUnusedBid); + .filter(bid => bid); }; auctionManager.getAdUnits = function() { diff --git a/src/config.js b/src/config.js index bbde171576c..eb44aba3beb 100644 --- a/src/config.js +++ b/src/config.js @@ -14,7 +14,7 @@ const DEFAULT_DEBUG = false; const DEFAULT_BIDDER_TIMEOUT = 3000; const DEFAULT_PUBLISHER_DOMAIN = window.location.origin; const DEFAULT_COOKIESYNC_DELAY = 100; -const DEFAULT_ENABLE_SEND_ALL_BIDS = false; +const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_USERSYNC = { syncEnabled: true, pixelEnabled: true, diff --git a/src/prebid.js b/src/prebid.js index 2a270b97558..3d7e777bad9 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -113,22 +113,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); - return targeting.getAllTargeting(adUnitCode) - .map(targeting => { - return { - [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] - .map(target => { - return { - [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') - }; - }).reduce((p, c) => Object.assign(c, p), {}) - }; - }) - .reduce(function (accumulator, targeting) { - var key = Object.keys(targeting)[0]; - accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); - return accumulator; - }, {}); + return targeting.getAllTargeting(adUnitCode); }; /** @@ -190,7 +175,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit) { targeting.resetPresetTargeting(adUnit); // now set new targeting keys - targeting.setTargeting(targetingSet); + targeting.setTargetingForGPT(targetingSet); // emit event events.emit(SET_TARGETING); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 86e24f17c28..fef2f40e25e 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -6,6 +6,7 @@ import events from './events'; import { fireNativeTrackers } from './native'; import { EVENTS } from './constants'; +import { isSlotMatchingAdUnitCode } from './utils'; import { auctionManager } from './auctionManager'; const BID_WON = EVENTS.BID_WON; @@ -67,11 +68,11 @@ function sendAdToCreative(adObject, remoteDomain, source) { } function resizeRemoteCreative({ adUnitCode, width, height }) { - const iframe = document.getElementById(window.googletag.pubads() - .getSlots().find(slot => { - return slot.getAdUnitPath() === adUnitCode || - slot.getSlotElementId() === adUnitCode; - }).getSlotElementId()).querySelector('iframe'); + const iframe = document.getElementById( + window.googletag.pubads().getSlots() + .filter(isSlotMatchingAdUnitCode(adUnitCode)) + .find(slot => slot) + .getSlotElementId()).querySelector('iframe'); iframe.width = '' + width; iframe.height = '' + height; diff --git a/src/targeting.js b/src/targeting.js index e4d607ff0ae..bb1257be8ec 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,4 +1,4 @@ -import { uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter, groupBy } from './utils'; +import { uniques, isGptPubadsDefined, getHighestCpm, groupBy, isAdUnitCodeMatchingSlot, timestamp } from './utils'; import { config } from './config'; import { NATIVE_TARGETING_KEYS } from './native'; import { auctionManager } from './auctionManager'; @@ -10,8 +10,26 @@ var pbTargetingKeys = []; export const BID_TARGETING_SET = 'targetingSet'; +const MAX_DFP_KEYLENGTH = 20; + +// return unexpired bids +export const isBidExpired = (bid) => (timestamp() - bid.responseTimestamp) < bid.ttl * 1000; + +// return bids whose status is not set. Winning bid can have status `targetingSet` or `rendered`. +const isUnusedBid = (bid) => bid && ((bid.status && bid.status === BID_TARGETING_SET) || !bid.status); + +/** + * @typedef {Object.} targeting + * @property {string} targeting_key + */ + +/** + * @typedef {Object.[]>[]} targetingArray + */ + export function newTargeting(auctionManager) { let targeting = {}; + targeting.resetPresetTargeting = function(adUnitCode) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); @@ -30,13 +48,18 @@ export function newTargeting(auctionManager) { } }; + /** + * Returns all ad server targeting for all ad units. + * @param {string=} adUnitCode + * @return {Object.} targeting + */ targeting.getAllTargeting = function(adUnitCode) { const adUnitCodes = getAdUnitCodes(adUnitCode); // 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. var targeting = getWinningBidTargeting(adUnitCodes) - .concat(getAlwaysUseBidTargeting(adUnitCodes)) + .concat(getCustomBidTargeting(adUnitCodes)) .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes) : []); // store a reference of the targeting keys @@ -49,25 +72,75 @@ export function newTargeting(auctionManager) { }); }); }); + + targeting = flattenTargeting(targeting); return targeting; }; - targeting.setTargeting = function(targetingConfig) { + /** + * Converts targeting array and flattens to make it easily iteratable + * e.g: Sample input to this function + * ``` + * [ + * { + * "div-gpt-ad-1460505748561-0": [{"hb_bidder": ["appnexusAst"]}] + * }, + * { + * "div-gpt-ad-1460505748561-0": [{"hb_bidder_appnexusAs": ["appnexusAst"]}] + * } + * ] + * ``` + * Resulting array + * ``` + * { + * "div-gpt-ad-1460505748561-0": { + * "hb_bidder": "appnexusAst", + * "hb_bidder_appnexusAs": "appnexusAst" + * } + * } + * ``` + * + * @param {targetingArray} targeting + * @return {Object.} targeting + */ + function flattenTargeting(targeting) { + let targetingObj = targeting.map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }).reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {}); + return targetingObj; + } + + /** + * Sets targeting for DFP + * @param {Object.>} targetingConfig + */ + targeting.setTargetingForGPT = function(targetingConfig) { window.googletag.pubads().getSlots().forEach(slot => { - targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || - Object.keys(targeting)[0] === slot.getSlotElementId()) - .forEach(targeting => targeting[Object.keys(targeting)[0]] - .forEach(key => { - key[Object.keys(key)[0]] - .map((value) => { - utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); - return value; - }) - .forEach(value => { - slot.setTargeting(Object.keys(key)[0], value); - }); - })); - }); + Object.keys(targetingConfig).filter(isAdUnitCodeMatchingSlot(slot)) + .forEach(targetId => + Object.keys(targetingConfig[targetId]).forEach(key => { + let valueArr = targetingConfig[targetId][key].split(','); + valueArr = (valueArr.length > 1) ? [valueArr] : valueArr; + valueArr.map((value) => { + utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${key} value: ${value}`); + return value; + }).forEach(value => { + slot.setTargeting(key, value); + }); + }) + ) + }) }; /** @@ -84,6 +157,12 @@ export function newTargeting(auctionManager) { return auctionManager.getAdUnitCodes() || []; } + function getBidsReceived() { + return auctionManager.getBidsReceived() + .filter(isUnusedBid) + .filter(exports.isBidExpired) + } + /** * Returns top bids for a given adUnit or set of adUnits. * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes @@ -92,33 +171,41 @@ export function newTargeting(auctionManager) { targeting.getWinningBids = function(adUnitCode) { const adUnitCodes = getAdUnitCodes(adUnitCode); - return auctionManager.getBidsReceived() + return getBidsReceived() .filter(bid => adUnitCodes.includes(bid.adUnitCode)) .filter(bid => bid.cpm > 0) .map(bid => bid.adUnitCode) .filter(uniques) - .map(adUnitCode => auctionManager.getBidsReceived() + .map(adUnitCode => getBidsReceived() .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) .reduce(getHighestCpm, getEmptyBid(adUnitCode))); }; + /** + * Sets targeting for AST + */ targeting.setTargetingForAst = function() { - let targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); - Object.keys(targeting).forEach(targetId => - Object.keys(targeting[targetId]).forEach(key => { - utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${targeting[targetId][key]}`); + let astTargeting = targeting.getAllTargeting(); + Object.keys(astTargeting).forEach(targetId => + Object.keys(astTargeting[targetId]).forEach(key => { + utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${astTargeting[targetId][key]}`); // setKeywords supports string and array as value - if (utils.isStr(targeting[targetId][key]) || utils.isArray(targeting[targetId][key])) { + if (utils.isStr(astTargeting[targetId][key]) || utils.isArray(astTargeting[targetId][key])) { let keywordsObj = {}; let input = 'hb_adid'; let nKey = (key.substring(0, input.length) === input) ? key.toUpperCase() : key; - keywordsObj[nKey] = targeting[targetId][key]; + keywordsObj[nKey] = astTargeting[targetId][key]; window.apntag.setKeywords(targetId, keywordsObj); } }) ); }; + /** + * Get targeting key value pairs for winning bid. + * @param {string[]} AdUnit code array + * @return {targetingArray} winning bids targeting + */ function getWinningBidTargeting(adUnitCodes) { let winners = targeting.getWinningBids(adUnitCodes); winners.forEach((winner) => { @@ -135,7 +222,9 @@ export function newTargeting(auctionManager) { typeof winner.sendStandardTargeting === 'undefined' || winner.sendStandardTargeting || standardKeys.indexOf(key) === -1) - .map(key => ({ [key.substring(0, 20)]: [winner.adserverTargeting[key]] })) + .map(key => ({ + [(key === 'hb_deal') ? `${key}_${winner.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH) : key.substring(0, MAX_DFP_KEYLENGTH)]: [winner.adserverTargeting[key]] + })) }; }); @@ -149,36 +238,88 @@ export function newTargeting(auctionManager) { } /** - * Get custom targeting keys for bids that have `alwaysUseBid=true`. + * Merge custom adserverTargeting with same key name for same adUnitCode. + * e.g: Appnexus defining custom keyvalue pair foo:bar and Rubicon defining custom keyvalue pair foo:baz will be merged to foo: ['bar','baz'] + * + * @param {Object[]} acc Accumulator for reducer. It will store updated bidResponse objects + * @param {Object} bid BidResponse + * @param {number} index current index + * @param {Array} arr original array */ - function getAlwaysUseBidTargeting(adUnitCodes) { + function mergeAdServerTargeting(acc, bid, index, arr) { + function concatTargetingValue(key) { + return function(currentBidElement) { + if (!utils.isArray(currentBidElement.adserverTargeting[key])) { + currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]]; + } + currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques); + delete bid.adserverTargeting[key]; + } + } + + function hasSameAdunitCodeAndKey(key) { + return function(currentBidElement) { + return currentBidElement.adUnitCode === bid.adUnitCode && currentBidElement.adserverTargeting[key] + } + } + + Object.keys(bid.adserverTargeting) + .filter(getCustomKeys()) + .forEach(key => { + if (acc.length) { + acc.filter(hasSameAdunitCodeAndKey(key)) + .forEach(concatTargetingValue(key)); + } + }); + acc.push(bid); + return acc; + } + + function getCustomKeys() { let standardKeys = getStandardKeys(); + return function(key) { + return standardKeys.indexOf(key) === -1; + } + } - return auctionManager.getBidsReceived() - .filter(adUnitsFilter.bind(this, adUnitCodes)) - .map(bid => { - if (bid.alwaysUseBid) { + function truncateCustomKeys(bid) { + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting) + // Get only the non-standard keys of the losing bids, since we + // don't want to override the standard keys of the winning bid. + .filter(getCustomKeys()) + .map(key => { return { - [bid.adUnitCode]: Object.keys(bid.adserverTargeting).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 + [key.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] }; - } - }) + }) + } + } + + /** + * Get custom targeting key value pairs for bids. + * @param {string[]} AdUnit code array + * @return {targetingArray} bids with custom targeting defined in bidderSettings + */ + function getCustomBidTargeting(adUnitCodes) { + return getBidsReceived() + .filter(bid => adUnitCodes.includes(bid.adUnitCode)) + .map(bid => Object.assign({}, bid)) + .reduce(mergeAdServerTargeting, []) + .map(truncateCustomKeys) .filter(bid => bid); // removes empty elements in array; } + /** + * Get targeting key value pairs for non-winning bids. + * @param {string[]} AdUnit code array + * @return {targetingArray} all non-winning bids targeting + */ function getBidLandscapeTargeting(adUnitCodes) { const standardKeys = CONSTANTS.TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); const bids = []; // bucket by adUnitcode - let buckets = groupBy(auctionManager.getBidsReceived(), 'adUnitCode'); + let buckets = groupBy(getBidsReceived(), 'adUnitCode'); // filter top bid for each bucket by bidder Object.keys(buckets).forEach(bucketKey => { let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); @@ -199,7 +340,7 @@ export function newTargeting(auctionManager) { function getTargetingMap(bid, keys) { return keys.map(key => { return { - [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + [`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] }; }); } diff --git a/src/utils.js b/src/utils.js index a6dc8c94890..42fe274507d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -804,3 +804,23 @@ export function getOrigin() { return window.location.origin; } } + +const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode; + +/** + * Returns filter function to match adUnitCode in slot + * @param {object} slot GoogleTag slot + * @return {function} filter function + */ +export function isAdUnitCodeMatchingSlot(slot) { + return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode); +} + +/** + * Returns filter function to match adUnitCode in slot + * @param {string} adUnitCode AdUnit code + * @return {function} filter function + */ +export function isSlotMatchingAdUnitCode(adUnitCode) { + return (slot) => compareCodeAndSlot(slot, adUnitCode); +} diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 8108da3c555..8dbbe265cca 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -317,7 +317,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '0x0', 'foobar': '0x0' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'appnexus', @@ -346,7 +349,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'appnexus', @@ -375,7 +381,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '728x90', 'foobar': '728x90' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pagescience', @@ -403,7 +412,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brightcom', @@ -430,7 +442,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brealtime', @@ -458,7 +473,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pubmatic', @@ -487,7 +505,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'rubicon', @@ -514,7 +535,10 @@ export function getBidResponses() { 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 } ]; } @@ -1015,7 +1039,10 @@ export function getBidResponsesFromAPI() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brealtime', @@ -1043,7 +1070,10 @@ export function getBidResponsesFromAPI() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pubmatic', @@ -1072,7 +1102,10 @@ export function getBidResponsesFromAPI() { 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'rubicon', @@ -1099,7 +1132,10 @@ export function getBidResponsesFromAPI() { 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 } ] } @@ -1110,7 +1146,7 @@ export function getBidResponsesFromAPI() { export function getAdServerTargeting() { return { '/19968336/header-bid-tag-0': { - 'foobar': '300x250', + 'foobar': '0x0,300x250,300x600', 'hb_size': '300x250', 'hb_pb': '10.00', 'hb_adid': '233bcbee889d46d', @@ -1179,11 +1215,7 @@ export function getTargetingKeys() { ], [ 'foobar', - '300x250' - ], - [ - 'foobar', - '300x250' + ['0x0', '300x250', '300x600'] ] ]; } @@ -1197,7 +1229,7 @@ export function getTargetingKeysBidLandscape() { 'appnexus' ], [ - 'hb_adid', + 'hb_adid_appnexus', '233bcbee889d46d' ], [ @@ -1210,11 +1242,7 @@ export function getTargetingKeysBidLandscape() { ], [ 'foobar', - '300x250' - ], - [ - 'foobar', - '300x250' + ['0x0', '300x250', '300x600'] ], [ 'hb_bidder_triplelift', @@ -1236,10 +1264,6 @@ export function getTargetingKeysBidLandscape() { 'hb_bidder_appnexus', 'appnexus' ], - [ - 'hb_adid_appnexus', - '233bcbee889d46d' - ], [ 'hb_pb_appnexus', '10.00' diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index a3cfa88db82..42b89613e87 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -293,11 +293,10 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); - it('alwaysUseBid=true, sendStandardTargeting=false, and inherit custom', function () { + it('sendStandardTargeting=false, and inherit custom', function () { $$PREBID_GLOBAL$$.bidderSettings = { appnexus: { - alwaysUseBid: true, sendStandardTargeting: false, adserverTargeting: [ { @@ -328,7 +327,6 @@ describe('auctionmanager.js', function () { }; var response = getKeyValueTargetingPairs(bidderCode, bid); assert.deepEqual(response, expected); - assert.equal(bid.alwaysUseBid, true); assert.equal(bid.sendStandardTargeting, false); }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index ede319d8d4b..4e95139a875 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config'; import { getAdUnits } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import { auctionManager } from 'src/auctionManager'; +import * as targetingModule from 'src/targeting'; const bid1 = { 'bidderCode': 'rubicon', @@ -29,7 +30,10 @@ const bid1 = { 'hb_adid': '148018fe5e', 'hb_pb': '0.53', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }; const bid2 = { @@ -56,13 +60,18 @@ const bid2 = { 'hb_adid': '5454545', 'hb_pb': '0.25', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }; describe('targeting tests', () => { describe('getAllTargeting', () => { let amBidsReceivedStub; let amGetAdUnitsStub; + let bidExpiryStub; + beforeEach(() => { $$PREBID_GLOBAL$$._sendAllBids = false; amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived', function() { @@ -71,24 +80,23 @@ describe('targeting tests', () => { amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes', function() { return ['/123456/header-bid-tag-0']; }); + bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired', () => true); }); afterEach(() => { auctionManager.getBidsReceived.restore(); auctionManager.getAdUnitCodes.restore(); + targetingModule.isBidExpired.restore(); }); it('selects the top bid when _sendAllBids true', () => { config.setConfig({ enableSendAllBids: true }); let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); - let flattened = []; - targeting.filter(obj => obj['/123456/header-bid-tag-0'] !== undefined).forEach(item => flattened = flattened.concat(item['/123456/header-bid-tag-0'])); - let sendAllBidCpm = flattened.filter(obj => obj.hb_pb_rubicon !== undefined); - let winningBidCpm = flattened.filter(obj => obj.hb_pb !== undefined); + let sendAllBidCpm = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf('hb_pb_') != -1) // we shouldn't get more than 1 key for hb_pb_${bidder} expect(sendAllBidCpm.length).to.equal(1); // expect the winning CPM to be equal to the sendAllBidCPM - expect(sendAllBidCpm[0]['hb_pb_rubicon']).to.deep.equal(winningBidCpm[0]['hb_pb']); + expect(targeting['/123456/header-bid-tag-0']['hb_pb_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0']['hb_pb']); }); }); // end getAllTargeting tests }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 392b0087099..592f1c773be 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -12,7 +12,8 @@ import { targeting, newTargeting } from 'src/targeting'; import { config as configObj } from 'src/config'; import * as ajaxLib from 'src/ajax'; import * as auctionModule from 'src/auction'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; +import * as targetingModule from 'src/targeting'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -146,9 +147,16 @@ window.apntag = { }; describe('Unit: Prebid Module', function () { + let bidExpiryStub; + before(() => { + bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired', () => true); + }); + after(function() { $$PREBID_GLOBAL$$.adUnits = []; + targetingModule.isBidExpired.restore(); }); + describe('getAdserverTargetingForAdUnitCodeStr', function () { beforeEach(() => { resetAuction(); @@ -157,7 +165,7 @@ describe('Unit: Prebid Module', function () { it('should return targeting info as a string', function () { const adUnitCode = config.adUnitCodes[0]; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; var result = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr(adUnitCode); assert.equal(expected, result, 'returns expected string of ad targeting info'); }); @@ -202,7 +210,7 @@ describe('Unit: Prebid Module', function () { var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', + foobar: '0x0,300x250,300x600', hb_size: '300x250', hb_pb: '10.00', hb_adid: '233bcbee889d46d', @@ -226,14 +234,13 @@ describe('Unit: Prebid Module', function () { assert.deepEqual(targeting, expected); }); - it("should include a losing bid's custom ad targeting key when the bid has `alwaysUseBid` set to `true`", () => { + it("should include a losing bid's custom ad targeting key", () => { // Let's make sure we're getting the expected losing bid. assert.equal(auction.getBidsReceived()[0]['bidderCode'], 'triplelift'); assert.equal(auction.getBidsReceived()[0]['cpm'], 0.112256); // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. let _bidsReceived = getBidResponses(); - _bidsReceived[0]['alwaysUseBid'] = true; _bidsReceived[0]['adserverTargeting'] = { always_use_me: 'abc', }; @@ -241,6 +248,7 @@ describe('Unit: Prebid Module', function () { auction.getBidsReceived = function() { return _bidsReceived }; var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); + // Ensure targeting for both ad placements includes the custom key. assert.equal( targeting['/19968336/header-bid-tag-0'].hasOwnProperty('always_use_me'), @@ -249,7 +257,7 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', + foobar: '300x250,300x600', hb_size: '300x250', hb_pb: '10.00', hb_adid: '233bcbee889d46d', @@ -264,17 +272,15 @@ describe('Unit: Prebid Module', function () { hb_bidder: 'appnexus' } }; - assert.deepEqual(targeting, expected); }); - it('should not overwrite winning bids custom keys targeting key when the bid has `alwaysUseBid` set to `true`', () => { + it('should not overwrite winning bids custom keys targeting key', () => { resetAuction(); // mimic a bidderSetting.standard key here for each bid and alwaysUseBid true for every bid let _bidsReceived = getBidResponses(); _bidsReceived.forEach(bid => { bid.adserverTargeting.custom_ad_id = bid.adId; - bid.alwaysUseBid = true; }); auction.getBidsReceived = function() { return _bidsReceived }; @@ -343,15 +349,14 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', - custom_ad_id: '233bcbee889d46d' + foobar: '0x0,300x250,300x600', + custom_ad_id: '222bb26f9e8bd,233bcbee889d46d,25bedd4813632d7,26e0795ab963896,275bd666f5a5a5d,28f4039c636b6a7,29019e2ab586a5a' }, '/19968336/header-bid-tag1': { foobar: '728x90', custom_ad_id: '24bd938435ec3fc' } }; - assert.deepEqual(targeting, expected); }); }); @@ -477,32 +482,28 @@ describe('Unit: Prebid Module', function () { RESPONSE.tags[0].ads[0].cpm = 2.1234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - let bid = bidTargeting[0]['div-gpt-ad-1460505748561-0'].filter(obj => obj.hb_pb !== undefined); - expect(bid[0]['hb_pb'][0]).to.equal('2.12'); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('2.12'); }); it('should get correct hb_pb when using bid.cpm is between 5 to 8', () => { RESPONSE.tags[0].ads[0].cpm = 6.78; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - let bid = bidTargeting[0]['div-gpt-ad-1460505748561-0'].filter(obj => obj.hb_pb !== undefined); - expect(bid[0]['hb_pb'][0]).to.equal('6.75'); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('6.75'); }); it('should get correct hb_pb when using bid.cpm is between 8 to 20', () => { RESPONSE.tags[0].ads[0].cpm = 19.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - let bid = bidTargeting[0]['div-gpt-ad-1460505748561-0'].filter(obj => obj.hb_pb !== undefined); - expect(bid[0]['hb_pb'][0]).to.equal('19.50'); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('19.50'); }); it('should get correct hb_pb when using bid.cpm is between 20 to 25', () => { RESPONSE.tags[0].ads[0].cpm = 21.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - let bid = bidTargeting[0]['div-gpt-ad-1460505748561-0'].filter(obj => obj.hb_pb !== undefined); - expect(bid[0]['hb_pb'][0]).to.equal('21.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('21.00'); }); }); @@ -578,6 +579,7 @@ describe('Unit: Prebid Module', function () { it('should set targeting from googletag data', function () { var slots = createSlotArray(); + slots[0].spySetTargeting.reset(); window.googletag.pubads().setSlots(slots); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); @@ -598,7 +600,7 @@ describe('Unit: Prebid Module', function () { expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); }); - it('should set targeting for bids with `alwaysUseBid=true`', function () { + it('should set targeting for bids', function () { // Make sure we're getting the expected losing bid. assert.equal(auctionManager.getBidsReceived()[0]['bidderCode'], 'triplelift'); assert.equal(auctionManager.getBidsReceived()[0]['cpm'], 0.112256); @@ -606,7 +608,6 @@ describe('Unit: Prebid Module', function () { resetAuction(); // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. let _bidsReceived = getBidResponses(); - _bidsReceived[0]['alwaysUseBid'] = true; _bidsReceived[0]['adserverTargeting'] = { always_use_me: 'abc', }; @@ -637,15 +638,11 @@ describe('Unit: Prebid Module', function () { ], [ 'foobar', - '300x250' + ['300x250', '300x600'] ], [ 'always_use_me', 'abc' - ], - [ - 'foobar', - '300x250' ] ]; @@ -1158,7 +1155,6 @@ describe('Unit: Prebid Module', function () { }); it('should not queue bid requests when a previous bid request is in process', () => { - // var clock = sinon.useFakeTimers(); var requestObj1 = { bidsBackHandler: function bidsBackHandlerCallback() {}, timeout: 2000, @@ -1176,13 +1172,13 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids(requestObj1); $$PREBID_GLOBAL$$.requestBids(requestObj2); - // clock.tick(requestObj1.timeout - 1); assert.ok(spyCallBids.calledTwice, 'When two requests for bids are made both should be' + ' callBids immediately'); - assert.deepEqual($$PREBID_GLOBAL$$.getAdserverTargeting(), { + let result = targeting.getAllTargeting(); // $$PREBID_GLOBAL$$.getAdserverTargeting(); + let expected = { '/19968336/header-bid-tag-0': { - 'foobar': '300x250', + 'foobar': '0x0,300x250,300x600', 'hb_size': '300x250', 'hb_pb': '10.00', 'hb_adid': '233bcbee889d46d', @@ -1195,7 +1191,8 @@ describe('Unit: Prebid Module', function () { 'hb_size': '728x90', 'foobar': '728x90' } - }, 'targeting info returned for current placements'); + } + assert.deepEqual(result, expected, 'targeting info returned for current placements'); }); }); }); @@ -1524,18 +1521,30 @@ describe('Unit: Prebid Module', function () { }); describe('setTargetingForAst', () => { + let targeting; + let auctionManagerInstance; + beforeEach(() => { resetAuction(); + auctionManagerInstance = newAuctionManager(); + sinon.stub(auctionManagerInstance, 'getBidsReceived', function() { + return [getBidResponses()[1]]; + }); + sinon.stub(auctionManagerInstance, 'getAdUnitCodes', function() { + return ['/19968336/header-bid-tag-0']; + }); + targeting = newTargeting(auctionManagerInstance); }); afterEach(() => { + auctionManagerInstance.getBidsReceived.restore(); + auctionManagerInstance.getAdUnitCodes.restore(); resetAuction(); }); it('should set targeting for appnexus apntag object', () => { + const bids = auctionManagerInstance.getBidsReceived(); const adUnitCode = '/19968336/header-bid-tag-0'; - const bidder = 'appnexus'; - const bids = auctionManager.getBidsReceived().filter(bid => (bid.adUnitCode === adUnitCode && bid.bidderCode === bidder)); var expectedAdserverTargeting = bids[0].adserverTargeting; var newAdserverTargeting = {}; @@ -1544,14 +1553,14 @@ describe('Unit: Prebid Module', function () { newAdserverTargeting[nkey] = expectedAdserverTargeting[key]; } - $$PREBID_GLOBAL$$.setTargetingForAst(); + targeting.setTargetingForAst(); expect(newAdserverTargeting).to.deep.equal(window.apntag.tags[adUnitCode].keywords); }); it('should not find hb_adid key in lowercase for all bidders', () => { const adUnitCode = '/19968336/header-bid-tag-0'; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - $$PREBID_GLOBAL$$.setTargetingForAst(); + targeting.setTargetingForAst(); const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, 'hb_adid'.length) === 'hb_adid')); expect(keywords.length).to.equal(0); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index a08abaee847..5747b1e1268 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -100,7 +100,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; assert.equal(output, expected); });