From b2cae07f165a12e7ac978287f85ffde5f756e0dd Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 May 2023 08:35:26 -0700 Subject: [PATCH] Remove Rubicon Analytics for Prebid 8 --- modules/rubiconAnalyticsAdapter.js | 938 ------ .../modules/rubiconAnalyticsAdapter_spec.js | 2540 ----------------- test/spec/modules/rubiconAnalyticsSchema.json | 494 ---- 3 files changed, 3972 deletions(-) delete mode 100644 modules/rubiconAnalyticsAdapter.js delete mode 100644 test/spec/modules/rubiconAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/rubiconAnalyticsSchema.json diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js deleted file mode 100644 index 7bbc435cb27..00000000000 --- a/modules/rubiconAnalyticsAdapter.js +++ /dev/null @@ -1,938 +0,0 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue, deepClone, logInfo } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'rubicon'}); -const COOKIE_NAME = 'rpaSession'; -const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins -const END_EXPIRE_TIME = 21600000; // 6 hours -const MODULE_NAME = 'Rubicon Analytics'; - -const pbsErrorMap = { - 1: 'timeout-error', - 2: 'input-error', - 3: 'connect-error', - 4: 'request-error', - 999: 'generic-error' -} - -let prebidGlobal = getGlobal(); -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_TIMEOUT, - BID_WON, - SET_TARGETING, - BILLABLE_EVENT - }, - STATUS: { - GOOD, - NO_BID - }, - BID_STATUS: { - BID_REJECTED - } -} = CONSTANTS; - -let serverConfig; -config.getConfig('s2sConfig', ({ s2sConfig }) => { - serverConfig = s2sConfig; -}); - -export const SEND_TIMEOUT = 3000; -const DEFAULT_INTEGRATION = 'pbjs'; - -const cache = { - auctions: {}, - targeting: {}, - timeouts: {}, - gpt: {}, - billing: {} -}; - -const BID_REJECTED_IPF = 'rejected-ipf'; - -export let rubiConf; -export const resetRubiConf = () => { - rubiConf = { - pvid: generateUUID().slice(0, 8), - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - } - } -} -resetRubiConf(); - -// we are saving these as global to this module so that if a pub accidentally overwrites the entire -// rubicon object, then we do not lose other data -config.getConfig('rubicon', config => { - mergeDeep(rubiConf, config.rubicon); - if (deepAccess(config, 'rubicon.updatePageView') === true) { - rubiConf.pvid = generateUUID().slice(0, 8) - } -}); - -export function getHostNameFromReferer(referer) { - try { - rubiconAdapter.referrerHostname = parseUrl(referer, { noDecodeWholeURL: true }).hostname; - } catch (e) { - logError(`${MODULE_NAME}: Unable to parse hostname from supplied url: `, referer, e); - rubiconAdapter.referrerHostname = ''; - } - return rubiconAdapter.referrerHostname -}; - -function stringProperties(obj) { - return Object.keys(obj).reduce((newObj, prop) => { - let value = obj[prop]; - if (typeof value === 'number') { - value = value.toFixed(3); - } else if (typeof value !== 'string') { - value = String(value); - } - newObj[prop] = value || undefined; - return newObj; - }, {}); -} - -function sizeToDimensions(size) { - return { - width: size.w || size[0], - height: size.h || size[1] - }; -} - -function validMediaType(type) { - return ['banner', 'native', 'video'].indexOf(type) !== -1; -} - -function formatSource(src) { - if (typeof src === 'undefined') { - src = 'client'; - } else if (src === 's2s') { - src = 'server'; - } - return src.toLowerCase(); -} - -function getBillingPayload(event) { - let billingEvent = deepClone(event); - // Pass along type if is string and not empty else general - billingEvent.type = (typeof event.type === 'string' && event.type) || 'general'; - billingEvent.accountId = accountId; - // mark as sent - deepSetValue(cache.billing, `${event.vendor}.${event.billingId}`, true); - return billingEvent; -} - -function sendBillingEvent(event) { - let message = getBasicEventDetails(undefined, 'soloBilling'); - message.billableEvents = [getBillingPayload(event)]; - ajax( - rubiconAdapter.getUrl(), - null, - JSON.stringify(message), - { - contentType: 'application/json' - } - ); -} - -function getBasicEventDetails(auctionId, trigger) { - let auctionCache = cache.auctions[auctionId]; - let referrer = pageReferer || (auctionCache && auctionCache.referrer); - let message = { - timestamps: { - prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, - auctionEnded: auctionCache ? auctionCache.endTs : undefined, - eventTime: Date.now() - }, - trigger, - integration: rubiConf.int_type || DEFAULT_INTEGRATION, - version: '$prebid.version$', - referrerUri: referrer, - referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), - channel: 'web', - }; - if (rubiConf.wrapperName) { - message.wrapper = { - name: rubiConf.wrapperName, - family: rubiConf.wrapperFamily, - rule: rubiConf.rule_name - } - } - return message; -} - -function sendMessage(auctionId, bidWonId, trigger) { - function formatBid(bid) { - return pick(bid, [ - 'bidder', - 'bidderDetail', - 'bidId', bidId => deepAccess(bid, 'bidResponse.pbsBidId') || deepAccess(bid, 'bidResponse.seatBidId') || bidId, - 'status', - 'error', - 'source', (source, bid) => { - if (source) { - return source; - } - return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.some(s2sBidder => s2sBidder.toLowerCase() === bid.bidder) !== -1 - ? 'server' : 'client' - }, - 'clientLatencyMillis', - 'serverLatencyMillis', - 'params', - 'bidResponse', bidResponse => bidResponse ? pick(bidResponse, [ - 'bidPriceUSD', - 'dealId', - 'dimensions', - 'mediaType', - 'floorValue', - 'floorRuleValue', - 'floorRule', - 'adomains' - ]) : undefined - ]); - } - function formatBidWon(bid) { - return Object.assign(formatBid(bid), pick(bid.adUnit, [ - 'adUnitCode', - 'transactionId', - 'videoAdFormat', () => bid.videoAdFormat, - 'mediaTypes' - ]), { - adserverTargeting: !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, - bidwonStatus: 'success', // hard-coded for now - accountId, - siteId: bid.siteId, - zoneId: bid.zoneId, - samplingFactor - }); - } - let message = getBasicEventDetails(auctionId, trigger); - let auctionCache = cache.auctions[auctionId]; - if (auctionCache && !auctionCache.sent) { - let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { - let bid = auctionCache.bids[bidId]; - let adUnit = adUnits[bid.adUnit.adUnitCode]; - if (!adUnit) { - adUnit = adUnits[bid.adUnit.adUnitCode] = pick(bid.adUnit, [ - 'adUnitCode', - 'transactionId', - 'mediaTypes', - 'dimensions', - 'adserverTargeting', () => !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, - 'gam', gam => !isEmpty(gam) ? gam : undefined, - 'pbAdSlot', - 'gpid', - 'pattern' - ]); - adUnit.bids = []; - adUnit.status = 'no-bid'; // default it to be no bid - } - - // Add site and zone id if not there and if we found a rubicon bidder - if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { - if (deepAccess(bid, 'params.accountId') == accountId) { - adUnit.accountId = parseInt(accountId); - adUnit.siteId = parseInt(deepAccess(bid, 'params.siteId')); - adUnit.zoneId = parseInt(deepAccess(bid, 'params.zoneId')); - } - } - - if (bid.videoAdFormat && !adUnit.videoAdFormat) { - adUnit.videoAdFormat = bid.videoAdFormat; - } - - // determine adUnit.status from its bid statuses. Use priority below to determine, higher index is better - let statusPriority = ['error', 'no-bid', 'success']; - if (statusPriority.indexOf(bid.status) > statusPriority.indexOf(adUnit.status)) { - adUnit.status = bid.status; - } - - adUnit.bids.push(formatBid(bid)); - - return adUnits; - }, {}); - - // We need to mark each cached bid response with its appropriate rubicon site-zone id - // This allows the bidWon events to have these params even in the case of a delayed render - Object.keys(auctionCache.bids).forEach(function (bidId) { - let adCode = auctionCache.bids[bidId].adUnit.adUnitCode; - Object.assign(auctionCache.bids[bidId], pick(adUnitMap[adCode], ['accountId', 'siteId', 'zoneId'])); - }); - - let auction = { - clientTimeoutMillis: auctionCache.timeout, - auctionStart: auctionCache.timestamp, - auctionEnd: auctionCache.endTs, - bidderOrder: auctionCache.bidderOrder, - samplingFactor, - accountId, - adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), - requestId: auctionId - }; - - // pick our of top level floor data we want to send! - if (auctionCache.floorData) { - if (auctionCache.floorData.location === 'noData') { - auction.floors = pick(auctionCache.floorData, [ - 'location', - 'fetchStatus', - 'floorProvider as provider' - ]); - } else { - auction.floors = pick(auctionCache.floorData, [ - 'location', - 'modelVersion as modelName', - 'modelWeight', - 'modelTimestamp', - 'skipped', - 'enforcement', () => deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), - 'dealsEnforced', () => deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), - 'skipRate', - 'fetchStatus', - 'floorMin', - 'floorProvider as provider' - ]); - } - } - - // gather gdpr info - if (auctionCache.gdprConsent) { - auction.gdpr = pick(auctionCache.gdprConsent, [ - 'gdprApplies as applies', - 'consentString', - 'apiVersion as version' - ]); - } - - // gather session info - if (auctionCache.session) { - message.session = pick(auctionCache.session, [ - 'id', - 'pvid', - 'start', - 'expires' - ]); - if (!isEmpty(auctionCache.session.fpkvs)) { - message.fpkvs = Object.keys(auctionCache.session.fpkvs).map(key => { - return { key, value: auctionCache.session.fpkvs[key] }; - }); - } - } - - if (serverConfig) { - auction.serverTimeoutMillis = serverConfig.timeout; - } - - if (auctionCache.userIds.length) { - auction.user = { ids: auctionCache.userIds }; - } - - message.auctions = [auction]; - - let bidsWon = Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { - let bidId = auctionCache.bidsWon[adUnitCode]; - if (bidId) { - memo.push(formatBidWon(auctionCache.bids[bidId])); - } - return memo; - }, []); - - if (bidsWon.length > 0) { - message.bidsWon = bidsWon; - } - - auctionCache.sent = true; - } else if (bidWonId && auctionCache && auctionCache.bids[bidWonId]) { - message.bidsWon = [ - formatBidWon(auctionCache.bids[bidWonId]) - ]; - } - - // if we have not sent any billingEvents send them - const pendingBillingEvents = getPendingBillingEvents(auctionCache); - if (pendingBillingEvents && pendingBillingEvents.length) { - message.billableEvents = pendingBillingEvents; - } - - ajax( - this.getUrl(), - null, - JSON.stringify(message), - { - contentType: 'application/json' - } - ); -} - -function getPendingBillingEvents(auctionCache) { - if (auctionCache && auctionCache.billing && auctionCache.billing.length) { - return auctionCache.billing.reduce((accum, billingEvent) => { - if (deepAccess(cache.billing, `${billingEvent.vendor}.${billingEvent.billingId}`) === false) { - accum.push(getBillingPayload(billingEvent)); - } - return accum; - }, []); - } -} - -function adUnitIsOnlyInstream(adUnit) { - return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; -} - -function getBidPrice(bid) { - // get the cpm from bidResponse - let cpm; - let currency; - if (bid.status === BID_REJECTED && deepAccess(bid, 'floorData.cpmAfterAdjustments')) { - // if bid was rejected and bid.floorData.cpmAfterAdjustments use it - cpm = bid.floorData.cpmAfterAdjustments; - currency = bid.floorData.floorCurrency; - } else if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === 'USD') { - // bid is in USD use it - return Number(bid.cpm); - } else { - // else grab cpm - cpm = bid.cpm; - currency = bid.currency; - } - // if after this it is still going and is USD then return it. - if (currency === 'USD') { - return Number(cpm); - } - // otherwise we convert and return - try { - return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); - } catch (err) { - logWarn(`${MODULE_NAME}: Could not determine the bidPriceUSD of the bid `, bid); - } -} - -export function parseBidResponse(bid, previousBidResponse) { - // The current bidResponse for this matching requestId/bidRequestId - let responsePrice = getBidPrice(bid) - // we need to compare it with the previous one (if there was one) - if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) { - return previousBidResponse; - } - return pick(bid, [ - 'bidPriceUSD', () => responsePrice, - 'dealId', - 'status', - 'mediaType', - 'dimensions', () => { - const width = bid.width || bid.playerWidth; - const height = bid.height || bid.playerHeight; - return (width && height) ? { width, height } : undefined; - }, - // Handling use case where pbs sends back 0 or '0' bidIds - 'pbsBidId', pbsBidId => pbsBidId == 0 ? generateUUID() : pbsBidId, - 'seatBidId', seatBidId => seatBidId == 0 ? generateUUID() : seatBidId, - 'floorValue', () => deepAccess(bid, 'floorData.floorValue'), - 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), - 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, - 'adomains', () => { - const adomains = deepAccess(bid, 'meta.advertiserDomains'); - const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string'); - return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined - } - ]); -} - -/* - Filters and converts URL Params into an object and returns only KVs that match the 'utm_KEY' format -*/ -function getUtmParams() { - let search; - - try { - search = parseQS(getWindowLocation().search); - } catch (e) { - search = {}; - } - - return Object.keys(search).reduce((accum, param) => { - if (param.match(/utm_/)) { - accum[param.replace(/utm_/, '')] = search[param]; - } - return accum; - }, {}); -} - -function getFpkvs() { - rubiConf.fpkvs = Object.assign((rubiConf.fpkvs || {}), getUtmParams()); - - // convert all values to strings - Object.keys(rubiConf.fpkvs).forEach(key => { - rubiConf.fpkvs[key] = rubiConf.fpkvs[key] + ''; - }); - - return rubiConf.fpkvs; -} - -let samplingFactor = 1; -let accountId; -// List of known rubicon aliases -// This gets updated on auction init to account for any custom aliases present -let rubiconAliases = ['rubicon']; - -/* - Checks the alias registry for any entries of the rubicon bid adapter. - adds to the rubiconAliases list if found -*/ -function setRubiconAliases(aliasRegistry) { - Object.keys(aliasRegistry).forEach(function (alias) { - if (aliasRegistry[alias] === 'rubicon') { - rubiconAliases.push(alias); - } - }); -} - -function getRpaCookie() { - let encodedCookie = storage.getDataFromLocalStorage(COOKIE_NAME); - if (encodedCookie) { - try { - return JSON.parse(window.atob(encodedCookie)); - } catch (e) { - logError(`${MODULE_NAME}: Unable to decode ${COOKIE_NAME} value: `, e); - } - } - return {}; -} - -function setRpaCookie(decodedCookie) { - try { - storage.setDataInLocalStorage(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); - } catch (e) { - logError(`${MODULE_NAME}: Unable to encode ${COOKIE_NAME} value: `, e); - } -} - -function updateRpaCookie() { - const currentTime = Date.now(); - let decodedRpaCookie = getRpaCookie(); - if ( - !Object.keys(decodedRpaCookie).length || - (currentTime - decodedRpaCookie.lastSeen) > LAST_SEEN_EXPIRE_TIME || - decodedRpaCookie.expires < currentTime - ) { - decodedRpaCookie = { - id: generateUUID(), - start: currentTime, - expires: currentTime + END_EXPIRE_TIME, // six hours later, - } - } - // possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception - if (Object.keys(decodedRpaCookie).length) { - decodedRpaCookie.lastSeen = currentTime; - decodedRpaCookie.fpkvs = { ...decodedRpaCookie.fpkvs, ...getFpkvs() }; - decodedRpaCookie.pvid = rubiConf.pvid; - setRpaCookie(decodedRpaCookie) - } - return decodedRpaCookie; -} - -function subscribeToGamSlots() { - window.googletag.pubads().addEventListener('slotRenderEnded', event => { - const isMatchingAdSlot = isAdUnitCodeMatchingSlot(event.slot); - // loop through auctions and adUnits and mark the info - // only mark first auction which finds a match - let hasMatch = false; - Object.keys(cache.auctions).find(auctionId => { - (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { - let bid = cache.auctions[auctionId].bids[bidId]; - // if this slot matches this bids adUnit, add the adUnit info - // only mark it if it already has not been marked - if (!bid.adUnit.gamRendered && isMatchingAdSlot(bid.adUnit.adUnitCode)) { - // mark this adUnit as having been rendered by gam - cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; - - // this current auction has an adunit that matched the slot, so mark it as matched so next auciton is skipped - hasMatch = true; - - bid.adUnit.gam = pick(event, [ - // these come in as `null` from Gpt, which when stringified does not get removed - // so set explicitly to undefined when not a number - 'advertiserId', advertiserId => isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : isNumber(lineItemId) ? lineItemId : undefined, - 'adSlot', () => event.slot.getAdUnitPath(), - 'isSlotEmpty', () => event.isEmpty || undefined - ]); - - // this lets us know next iteration not to check this bids adunit - bid.adUnit.gamRendered = true; - } - }); - // Now if all adUnits have gam rendered, send the payload - if (rubiConf.waitForGamSlots && !cache.auctions[auctionId].sent && Object.keys(cache.auctions[auctionId].gamHasRendered).every(adUnitCode => cache.auctions[auctionId].gamHasRendered[adUnitCode])) { - clearTimeout(cache.timeouts[auctionId]); - delete cache.timeouts[auctionId]; - if (rubiConf.analyticsEventDelay > 0) { - setTimeout(() => sendMessage.call(rubiconAdapter, auctionId, undefined, 'delayedGam'), rubiConf.analyticsEventDelay) - } else { - sendMessage.call(rubiconAdapter, auctionId, undefined, 'gam') - } - } - return hasMatch; - }); - }); -} - -let pageReferer; - -const isBillingEventValid = event => { - // vendor is whitelisted - const isWhitelistedVendor = rubiConf.dmBilling.vendors.includes(event.vendor); - // event is not duplicated - const isNotDuplicate = typeof deepAccess(cache.billing, `${event.vendor}.${event.billingId}`) !== 'boolean'; - // billingId is defined and a string - return typeof event.billingId === 'string' && isWhitelistedVendor && isNotDuplicate; -} - -const sendOrAddEventToQueue = event => { - // if any auction is not sent yet, then add it to the auction queue - const pendingAuction = Object.keys(cache.auctions).find(auctionId => !cache.auctions[auctionId].sent); - - if (rubiConf.dmBilling.waitForAuction && pendingAuction) { - cache.auctions[pendingAuction].billing = cache.auctions[pendingAuction].billing || []; - cache.auctions[pendingAuction].billing.push(event); - } else { - // send it - sendBillingEvent(event); - } -} - -let baseAdapter = adapter({ analyticsType: 'endpoint' }); -let rubiconAdapter = Object.assign({}, baseAdapter, { - MODULE_INITIALIZED_TIME: Date.now(), - referrerHostname: '', - enableAnalytics(config = {}) { - let error = false; - samplingFactor = 1; - - if (typeof config.options === 'object') { - if (config.options.accountId) { - accountId = Number(config.options.accountId); - } - if (config.options.endpoint) { - this.getUrl = () => config.options.endpoint; - } else { - logError(`${MODULE_NAME}: required endpoint missing`); - error = true; - } - if (typeof config.options.sampling !== 'undefined') { - samplingFactor = 1 / parseFloat(config.options.sampling); - } - if (typeof config.options.samplingFactor !== 'undefined') { - if (typeof config.options.sampling !== 'undefined') { - logWarn(`${MODULE_NAME}: Both options.samplingFactor and options.sampling enabled defaulting to samplingFactor`); - } - samplingFactor = parseFloat(config.options.samplingFactor); - config.options.sampling = 1 / samplingFactor; - } - } - - let validSamplingFactors = [1, 10, 20, 40, 100]; - if (validSamplingFactors.indexOf(samplingFactor) === -1) { - error = true; - logError(`${MODULE_NAME}: invalid samplingFactor ${samplingFactor} - must be one of ${validSamplingFactors.join(', ')}`); - } else if (!accountId) { - error = true; - logError(`${MODULE_NAME}: required accountId missing for rubicon analytics`); - } - - if (!error) { - baseAdapter.enableAnalytics.call(this, config); - } - }, - disableAnalytics() { - this.getUrl = baseAdapter.getUrl; - accountId = undefined; - rubiConf = {}; - cache.gpt.registered = false; - cache.billing = {}; - baseAdapter.disableAnalytics.apply(this, arguments); - }, - track({ eventType, args }) { - switch (eventType) { - case AUCTION_INIT: - // set the rubicon aliases - setRubiconAliases(adapterManager.aliasRegistry); - let cacheEntry = pick(args, [ - 'timestamp', - 'timeout' - ]); - cacheEntry.bids = {}; - cacheEntry.bidsWon = {}; - cacheEntry.gamHasRendered = {}; - // TODO: is 'page' the right value here? - cacheEntry.referrer = pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.page'); - cacheEntry.bidderOrder = []; - const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); - if (floorData) { - cacheEntry.floorData = { ...floorData }; - } - cacheEntry.gdprConsent = deepAccess(args, 'bidderRequests.0.gdprConsent'); - cacheEntry.session = storage.localStorageIsEnabled() && updateRpaCookie(); - cacheEntry.userIds = Object.keys(deepAccess(args, 'bidderRequests.0.bids.0.userId', {})).map(id => { - return { provider: id, hasId: true } - }); - cache.auctions[args.auctionId] = cacheEntry; - // register to listen to gpt events if not done yet - if (!cache.gpt.registered && isGptPubadsDefined()) { - subscribeToGamSlots(); - cache.gpt.registered = true; - } else if (!cache.gpt.registered) { - cache.gpt.registered = true; - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function () { - subscribeToGamSlots(); - }); - } - break; - case BID_REQUESTED: - cache.auctions[args.auctionId].bidderOrder.push(args.bidderCode); - Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { - // mark adUnits we expect bidWon events for - cache.auctions[args.auctionId].bidsWon[bid.adUnitCode] = false; - - if (rubiConf.waitForGamSlots && !adUnitIsOnlyInstream(bid)) { - cache.auctions[args.auctionId].gamHasRendered[bid.adUnitCode] = false; - } - - memo[bid.bidId] = pick(bid, [ - 'bidder', bidder => bidder.toLowerCase(), - 'bidId', - 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out - 'source', () => formatSource(bid.src), - 'params', (params, bid) => { - switch (bid.bidder) { - // specify bidder params we want here - case 'rubicon': - return pick(params, [ - 'accountId', - 'siteId', - 'zoneId' - ]); - } - }, - 'videoAdFormat', (_, cachedBid) => { - if (cachedBid.bidder === 'rubicon') { - return ({ - 201: 'pre-roll', - 202: 'interstitial', - 203: 'outstream', - 204: 'mid-roll', - 205: 'post-roll', - 207: 'vertical' - })[deepAccess(bid, 'params.video.size_id')]; - } else { - let startdelay = parseInt(deepAccess(bid, 'params.video.startdelay'), 10); - if (!isNaN(startdelay)) { - if (startdelay > 0) { - return 'mid-roll'; - } - return ({ - '0': 'pre-roll', - '-1': 'mid-roll', - '-2': 'post-roll' - })[startdelay] - } - } - }, - 'adUnit', () => pick(bid, [ - 'adUnitCode', - 'transactionId', - 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), - 'mediaTypes', (types) => { - if (bid.mediaType && validMediaType(bid.mediaType)) { - return [bid.mediaType]; - } - if (Array.isArray(types)) { - return types.filter(validMediaType); - } - if (typeof types === 'object') { - if (!bid.sizes) { - bid.dimensions = []; - _each(types, (type) => - bid.dimensions = bid.dimensions.concat( - type.sizes.map(sizeToDimensions) - ) - ); - } - return Object.keys(types).filter(validMediaType); - } - return ['banner']; - }, - 'gam', () => { - if (deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { - return { adSlot: bid.ortb2Imp.ext.data.adserver.adslot } - } - }, - 'pbAdSlot', () => deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), - 'pattern', () => deepAccess(bid, 'ortb2Imp.ext.data.aupname'), - 'gpid', () => deepAccess(bid, 'ortb2Imp.ext.gpid') - ]) - ]); - return memo; - }, {})); - break; - case BID_RESPONSE: - let auctionEntry = cache.auctions[args.auctionId]; - - if (!auctionEntry.bids[args.requestId] && args.originalRequestId) { - auctionEntry.bids[args.requestId] = { ...auctionEntry.bids[args.originalRequestId] }; - auctionEntry.bids[args.requestId].bidId = args.requestId; - auctionEntry.bids[args.requestId].bidderDetail = args.targetingBidder; - } - - let bid = auctionEntry.bids[args.requestId]; - // If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name - if (!deepAccess(bid, 'adUnit.gam.adSlot') && deepAccess(args, 'floorData.matchedFields.gptSlot')) { - deepSetValue(bid, 'adUnit.gam.adSlot', args.floorData.matchedFields.gptSlot); - } - // if we have not set enforcements yet set it - if (!deepAccess(auctionEntry, 'floorData.enforcements') && deepAccess(args, 'floorData.enforcements')) { - auctionEntry.floorData.enforcements = { ...args.floorData.enforcements }; - } - if (!bid) { - logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); - break; - } - bid.source = formatSource(bid.source || args.source); - switch (args.getStatusCode()) { - case GOOD: - bid.status = 'success'; - delete bid.error; // it's possible for this to be set by a previous timeout - break; - case NO_BID: - bid.status = args.status === BID_REJECTED ? BID_REJECTED_IPF : 'no-bid'; - delete bid.error; - break; - default: - bid.status = 'error'; - bid.error = { - code: 'request-error' - }; - } - bid.clientLatencyMillis = bid.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp; - bid.bidResponse = parseBidResponse(args, bid.bidResponse); - break; - case BIDDER_DONE: - const serverError = deepAccess(args, 'serverErrors.0'); - const serverResponseTimeMs = args.serverResponseTimeMs; - args.bids.forEach(bid => { - let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; - if (typeof bid.serverResponseTimeMs !== 'undefined') { - cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; - } else if (serverResponseTimeMs && bid.source === 's2s') { - cachedBid.serverLatencyMillis = serverResponseTimeMs; - } - // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET - if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { - cachedBid.status = 'error'; - cachedBid.error = { - code: pbsErrorMap[serverError.code] || pbsErrorMap[999], - description: serverError.message - } - } - if (!cachedBid.status) { - cachedBid.status = 'no-bid'; - } - if (!cachedBid.clientLatencyMillis) { - cachedBid.clientLatencyMillis = Date.now() - cache.auctions[bid.auctionId].timestamp; - } - }); - break; - case SET_TARGETING: - Object.assign(cache.targeting, args); - break; - case BID_WON: - let auctionCache = cache.auctions[args.auctionId]; - auctionCache.bidsWon[args.adUnitCode] = args.requestId; - - // check if this BID_WON missed the boat, if so send by itself - if (auctionCache.sent === true) { - sendMessage.call(this, args.auctionId, args.requestId, 'soloBidWon'); - } else if (!rubiConf.waitForGamSlots && Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { - // only send if we've received bidWon events for all adUnits in auction - memo = memo && auctionCache.bidsWon[adUnitCode]; - return memo; - }, true)) { - clearTimeout(cache.timeouts[args.auctionId]); - delete cache.timeouts[args.auctionId]; - - sendMessage.call(this, args.auctionId, undefined, 'allBidWons'); - } - break; - case AUCTION_END: - // see how long it takes for the payload to come fire - let auctionData = cache.auctions[args.auctionId]; - // if for some reason the auction did not do its normal thing, this could be undefied so bail - if (!auctionData) { - break; - } - auctionData.endTs = Date.now(); - - const isOnlyInstreamAuction = args.adUnits && args.adUnits.every(adUnit => adUnitIsOnlyInstream(adUnit)); - // If only instream, do not wait around, just send payload - if (isOnlyInstreamAuction) { - sendMessage.call(this, args.auctionId, undefined, 'instreamAuction'); - } else { - // start timer to send batched payload just in case we don't hear any BID_WON events - cache.timeouts[args.auctionId] = setTimeout(() => { - sendMessage.call(this, args.auctionId, undefined, 'auctionEnd'); - }, rubiConf.analyticsBatchTimeout || SEND_TIMEOUT); - } - break; - case BID_TIMEOUT: - args.forEach(badBid => { - let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.bids[badBid.bidId || badBid.requestId]; - // might be set already by bidder-done, so do not overwrite - if (bid.status !== 'error') { - bid.status = 'error'; - bid.error = { - code: 'timeout-error', - description: 'prebid.js timeout' // will help us diff if timeout was set by PBS or PBJS - }; - } - }); - break; - case BILLABLE_EVENT: - if (rubiConf.dmBilling.enabled && isBillingEventValid(args)) { - // add to the map indicating it has not been sent yet - deepSetValue(cache.billing, `${args.vendor}.${args.billingId}`, false); - sendOrAddEventToQueue(args); - } else { - logInfo(`${MODULE_NAME}: Billing event ignored`, args); - } - } - } -}); - -adapterManager.registerAnalyticsAdapter({ - adapter: rubiconAdapter, - code: 'rubicon', - gvlid: RUBICON_GVL_ID -}); - -export default rubiconAdapter; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js deleted file mode 100644 index 05b6cc6b6f4..00000000000 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ /dev/null @@ -1,2540 +0,0 @@ -import rubiconAnalyticsAdapter, { - SEND_TIMEOUT, - parseBidResponse, - getHostNameFromReferer, - storage, - rubiConf, - resetRubiConf -} from 'modules/rubiconAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; -import * as mockGpt from '../integration/faker/googletag.js'; -import { - setConfig, - addBidResponseHook, -} from 'modules/currency.js'; - -let Ajv = require('ajv'); -let schema = require('./rubiconAnalyticsSchema.json'); -let ajv = new Ajv({ - allErrors: true -}); - -let validator = ajv.compile(schema); - -function validate(message) { - validator(message); - expect(validator.errors).to.deep.equal(null); -} - -let events = require('src/events.js'); -let utils = require('src/utils.js'); - -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - SET_TARGETING, - BILLABLE_EVENT - } -} = CONSTANTS; - -const BID = { - 'bidder': 'rubicon', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'statusMessage': 'Bid available', - 'bidId': '2ecff0db240757', - 'adId': 'fake_ad_id', - 'source': 'client', - 'requestId': '2ecff0db240757', - 'currency': 'USD', - 'creativeId': '3571560', - 'cpm': 1.22752, - 'ttl': 300, - 'netRevenue': false, - 'ad': '', - 'rubiconTargeting': { - 'rpfl_elemid': '/19968336/header-bid-tag-0', - 'rpfl_14062': '2_tier0100' - }, - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'responseTimestamp': 1519149629415, - 'requestTimestamp': 1519149628471, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 944, - 'pbLg': '1.00', - 'pbMg': '1.20', - 'pbHg': '1.22', - 'pbAg': '1.20', - 'pbDg': '1.22', - 'pbCg': '', - 'size': '640x480', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': 1.20, - 'hb_size': '640x480', - 'hb_source': 'client' - }, - getStatusCode() { - return 1; - } -}; - -const BID2 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const BID3 = Object.assign({}, BID, { - adUnitCode: '/19968336/siderail-tag1', - bidId: '5fg6hyy4r879f0', - adId: 'fake_ad_id', - requestId: '5fg6hyy4r879f0', - width: 300, - height: 250, - mediaType: 'banner', - cpm: 2.01, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/siderail-tag1', - 'rpfl_14062': '15_tier0200' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '5fg6hyy4r879f0', - 'hb_pb': '2.00', - 'hb_size': '300x250', - 'hb_source': 'server' - } -}); - -const BID4 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - pbsBidId: 'zzzz-yyyy-xxxx-wwww', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const floorMinRequest = { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': '/19968336/siderail-tag1', - 'transactionId': 'c435626g-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[300, 250]], - 'bidId': '5fg6hyy4r879f0', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' -}; - -const MOCK = { - SET_TARGETING: { - [BID.adUnitCode]: BID.adserverTargeting, - [BID2.adUnitCode]: BID2.adserverTargeting, - [BID3.adUnitCode]: BID3.adserverTargeting - }, - AUCTION_INIT: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timestamp': 1519767010567, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag1', - 'sizes': [[640, 480]], - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - } - }], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' - } - ], - 'adUnitCodes': ['/19968336/header-bid-tag1'], - 'bidderRequests': [{ - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - } - ], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 3000, - 'config': { - 'accountId': 1001, 'endpoint': '//localhost:9999/event' - } - }, - BID_REQUESTED: { - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [ - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': 'test', - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'btf', - 'video': { - 'language': 'en', - 'playerHeight': 480, - 'playerWidth': 640, - 'size_id': 203, - 'skip': 1, - 'skipdelay': 15, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - } - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': [640, 480] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client' - }, - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[1000, 300], [970, 250], [728, 90]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[1000, 300], [970, 250], [728, 90]], - 'bidId': '3bd4ebb1c900e2', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 's2s' - } - ], - 'auctionStart': 1519149536560, - 'timeout': 5000, - 'start': 1519149562216, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - }, - BID_RESPONSE: [ - BID, - BID2, - BID3 - ], - AUCTION_END: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - }, - BID_WON: [ - Object.assign({}, BID, { - 'status': 'rendered' - }), - Object.assign({}, BID2, { - 'status': 'rendered' - }), - Object.assign({}, BID3, { - 'status': 'rendered' - }) - ], - BIDDER_DONE: { - 'bidderCode': 'rubicon', - 'serverResponseTimeMs': 42, - 'bids': [ - BID, - Object.assign({}, BID2, { - 'serverResponseTimeMs': 42, - }), - Object.assign({}, BID3, { - 'serverResponseTimeMs': 55, - }) - ] - }, - BID_TIMEOUT: [ - { - 'bidId': '2ecff0db240757', - 'bidder': 'rubicon', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - } - ] -}; - -const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; - -const ANALYTICS_MESSAGE = { - 'channel': 'web', - 'integration': 'pbjs', - 'version': '$prebid.version$', - 'referrerUri': 'http://www.test.com/page.html', - 'session': { - 'expires': 1519788613781, - 'id': STUBBED_UUID, - 'start': 1519767013781 - }, - 'timestamps': { - 'auctionEnded': 1519767013781, - 'eventTime': 1519767013781, - 'prebidLoaded': rubiconAnalyticsAdapter.MODULE_INITIALIZED_TIME - }, - 'trigger': 'allBidWons', - 'referrerHostname': 'www.test.com', - 'auctions': [ - { - - 'auctionEnd': 1519767013781, - 'auctionStart': 1519767010567, - 'bidderOrder': ['rubicon'], - 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'clientTimeoutMillis': 3000, - 'serverTimeoutMillis': 1000, - 'accountId': 1001, - 'samplingFactor': 1, - 'adUnits': [ - { - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'dimensions': [ - { - 'width': 640, - 'height': 480 - } - ], - 'status': 'success', - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - } - } - ] - }, - { - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'mediaTypes': [ - 'banner' - ], - 'dimensions': [ - { - 'width': 1000, - 'height': 300 - }, - { - 'width': 970, - 'height': 250 - }, - { - 'width': 728, - 'height': 90 - } - ], - 'status': 'success', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - } - } - ] - } - ] - } - ], - 'bidsWon': [ - { - 'bidder': 'rubicon', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'samplingFactor': 1, - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - }, - 'bidwonStatus': 'success' - }, - { - 'bidder': 'rubicon', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'adUnitCode': '/19968336/header-bid-tag1', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'samplingFactor': 1, - 'accountId': 1001, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'mediaTypes': [ - 'banner' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - }, - 'bidwonStatus': 'success' - } - ], - 'wrapper': { - 'name': '10000_fakewrapper_test' - } -}; - -function performStandardAuction(gptEvents, auctionId = MOCK.AUCTION_INIT.auctionId) { - events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, auctionId }); - events.emit(BID_REQUESTED, { ...MOCK.BID_REQUESTED, auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[0], auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[1], auctionId }); - events.emit(BIDDER_DONE, { ...MOCK.BIDDER_DONE, auctionId }); - events.emit(AUCTION_END, { ...MOCK.AUCTION_END, auctionId }); - - if (gptEvents && gptEvents.length) { - gptEvents.forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); - } - - events.emit(SET_TARGETING, { ...MOCK.SET_TARGETING, auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[0], auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[1], auctionId }); -} - -describe('rubicon analytics adapter', function () { - let sandbox; - let clock; - let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; - beforeEach(function () { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - mockGpt.disable(); - sandbox = sinon.sandbox.create(); - - localStorageIsEnabledStub.returns(true); - - sandbox.stub(events, 'getEvents').returns([]); - - sandbox.stub(utils, 'generateUUID').returns(STUBBED_UUID); - - clock = sandbox.useFakeTimers(1519767013781); - - rubiconAnalyticsAdapter.referrerHostname = ''; - - config.setConfig({ - s2sConfig: { - timeout: 1000, - accountId: 10000, - }, - rubicon: { - wrapperName: '10000_fakewrapper_test' - } - }) - }); - - afterEach(function () { - sandbox.restore(); - config.resetConfig(); - mockGpt.enable(); - getDataFromLocalStorageStub.restore(); - setDataInLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - }); - - it('should require accountId', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event' - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - it('should require endpoint', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - accountId: 1001 - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - describe('config subscribe', function () { - it('should update the pvid if user asks', function () { - expect(utils.generateUUID.called).to.equal(false); - config.setConfig({ rubicon: { updatePageView: true } }); - expect(utils.generateUUID.called).to.equal(true); - }); - it('should merge in and preserve older set configs', function () { - resetRubiConf(); - config.setConfig({ - rubicon: { - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - }, - }); - - // update it with stuff - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb', - link: 'email' - }, - }); - - // overwriting specific edge keys should update them - config.setConfig({ - rubicon: { - fpkvs: { - link: 'iMessage', - source: 'twitter' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - link: 'iMessage', - source: 'twitter' - }, - }); - }); - }); - - describe('sampling', function () { - beforeEach(function () { - sandbox.stub(Math, 'random').returns(0.08); - sandbox.stub(utils, 'logError'); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - describe('with options.samplingFactor', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 10 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 20 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - describe('with options.sampling', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.1 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.05 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 1 / 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - }); - - describe('when handling events', function () { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - it('should build a batched message from prebid events', function () { - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(ANALYTICS_MESSAGE); - }); - - it('should pass along bidderOrder correctly', function () { - const appnexusBid = utils.deepClone(MOCK.BID_REQUESTED); - appnexusBid.bidderCode = 'appnexus'; - const pubmaticBid = utils.deepClone(MOCK.BID_REQUESTED); - pubmaticBid.bidderCode = 'pubmatic'; - const indexBid = utils.deepClone(MOCK.BID_REQUESTED); - indexBid.bidderCode = 'ix'; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, pubmaticBid); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_REQUESTED, indexBid); - events.emit(BID_REQUESTED, appnexusBid); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - clock.tick(SEND_TIMEOUT + 1000); - - let message = JSON.parse(server.requests[0].requestBody); - expect(message.auctions[0].bidderOrder).to.deep.equal([ - 'pubmatic', - 'rubicon', - 'ix', - 'appnexus' - ]); - }); - - it('should pass along user ids', function () { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].userId = { - criteoId: 'sadfe4334', - lotamePanoramaId: 'asdf3gf4eg', - pubcid: 'dsfa4545-svgdfs5', - sharedId: { id1: 'asdf', id2: 'sadf4344' } - }; - - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message.auctions[0].user).to.deep.equal({ - ids: [ - { provider: 'criteoId', 'hasId': true }, - { provider: 'lotamePanoramaId', 'hasId': true }, - { provider: 'pubcid', 'hasId': true }, - { provider: 'sharedId', 'hasId': true }, - ] - }); - }); - - it('should handle bidResponse dimensions correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // mock bid response with playerWidth and playerHeight (NO width and height) - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - delete bidResponse1.width; - delete bidResponse1.height; - bidResponse1.playerWidth = 640; - bidResponse1.playerHeight = 480; - - // mock bid response with no width height or playerwidth playerheight - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - delete bidResponse2.width; - delete bidResponse2.height; - delete bidResponse2.playerWidth; - delete bidResponse2.playerHeight; - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.dimensions).to.deep.equal({ - width: 640, - height: 480 - }); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); - }); - - it('should pass along adomians correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // 1 adomains - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: ['magnite.com'] - } - - // two adomains - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: ['prebid.org', 'magnite.com'] - } - - // make sure we only pass max 10 adomains - bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); - }); - - it('should NOT pass along adomians correctly when edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // empty => nothing - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [] - } - - // not array => nothing - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: 'prebid.org' - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.be.undefined; - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should NOT pass along adomians with other edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // should filter out non string values and pass valid ones - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] - } - - // array of arrays (as seen when passed by kargo bid adapter) - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: [['prebid.org']] - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should not pass empty adServerTargeting values', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - const mockTargeting = utils.deepClone(MOCK.SET_TARGETING); - mockTargeting['/19968336/header-bid-tag-0'].hb_test = ''; - mockTargeting['/19968336/header-bid-tag1'].hb_test = 'NOT_EMPTY'; - events.emit(SET_TARGETING, mockTargeting); - - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_test).to.be.undefined; - expect(message.auctions[0].adUnits[1].adserverTargeting.hb_test).to.equal('NOT_EMPTY'); - }); - - function performFloorAuction(provider) { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].floorData = { - skipped: false, - modelVersion: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - location: 'setConfig', - skipRate: 15, - fetchStatus: 'error', - floorProvider: provider - }; - let flooredResponse = { - ...BID, - floorData: { - floorValue: 4, - floorRule: '12345/sports|video', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.1, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/sports', - mediaType: 'video' - } - }, - status: 'bidRejected', - cpm: 0, - getStatusCode() { - return 2; - } - }; - - let notFlooredResponse = { - ...BID2, - floorData: { - floorValue: 1, - floorRule: '12345/news|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 1.55, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/news', - mediaType: 'banner' - } - } - }; - - let floorMinResponse = { - ...BID3, - floorData: { - floorValue: 1.5, - floorRuleValue: 1, - floorRule: '12345/entertainment|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.00, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/entertainment', - mediaType: 'banner' - } - } - }; - - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids.push(floorMinRequest) - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, flooredResponse); - events.emit(BID_RESPONSE, notFlooredResponse); - events.emit(BID_RESPONSE, floorMinResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[1]); - events.emit(BID_WON, MOCK.BID_WON[2]); - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - return message; - } - - it('should capture price floor information correctly', function () { - let message = performFloorAuction('rubicon'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'rubicon' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - it('should still send floor info if provider is not rubicon', function () { - let message = performFloorAuction('randomProvider'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'randomProvider' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - describe('with session handling', function () { - const expectedPvid = STUBBED_UUID.slice(0, 8); - beforeEach(function () { - config.setConfig({ rubicon: { updatePageView: true } }); - }); - - it('should not log any session data if local storage is not enabled', function () { - localStorageIsEnabledStub.returns(false); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.session; - delete expectedMessage.fpkvs; - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should should pass along custom rubicon kv and pvid when defined', function () { - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should convert kvs to strings before sending', function () { - config.setConfig({ - rubicon: { - fpkvs: { - number: 24, - boolean: false, - string: 'hello', - array: ['one', 2, 'three'], - object: { one: 'two' } - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'number', value: '24' }, - { key: 'boolean', value: 'false' }, - { key: 'string', value: 'hello' }, - { key: 'array', value: 'one,2,three' }, - { key: 'object', value: '[object Object]' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=other', 'pbjs_debug': 'true' }); - - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'other' }, - { key: 'link', value: 'email' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick up existing localStorage and use its values', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'tw' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'tw', link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should overwrite matching localstorge value and use its remaining values', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=fb&utm_click=dog' }); - - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw', link: 'email' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' }, - { key: 'click', value: 'dog' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if lastSeen > 30 mins ago and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519764313781, // 45 mins before "now" - expires: 1519785913781, // six hours later - lastSeen: 1519764313781, // 45 mins before "now" - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if past expires time and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519745353781, // 6 hours before "expires" - expires: 1519766953781, // little more than six hours ago - lastSeen: 1519767008781, // 5 seconds ago - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - }); - describe('with googletag enabled', function () { - let gptSlot0, gptSlot1; - let gptSlotRenderEnded0, gptSlotRenderEnded1; - beforeEach(function () { - mockGpt.enable(); - gptSlot0 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag-0' }); - gptSlotRenderEnded0 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot0, - isEmpty: false, - advertiserId: 1111, - sourceAgnosticCreativeId: 2222, - sourceAgnosticLineItemId: 3333 - } - }; - - gptSlot1 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag1' }); - gptSlotRenderEnded1 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 4444, - sourceAgnosticCreativeId: 5555, - sourceAgnosticLineItemId: 6666 - } - }; - }); - - afterEach(function () { - mockGpt.disable(); - }); - - it('should add necessary gam information if gpt is enabled and slotRender event emmited', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should handle empty gam renders', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: true - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - isSlotEmpty: true, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should still add gam ids if falsy', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - sourceAgnosticCreativeId: 0, - sourceAgnosticLineItemId: 0 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 0, - lineItemId: 0, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick backup Ids if no sourceAgnostic available first', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - lineItemId: 1234, - creativeId: 5678 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 5678, - lineItemId: 1234, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should correctly set adUnit for associated slots', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should only mark the first gam data not all matches', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true - } - }); - performStandardAuction(); - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1], '32d332de-123a-32dg-2345-cefef3423324'); - - // tick the clock and both should fire - clock.tick(3000); - - expect(server.requests.length).to.equal(2); - - // first one should have GAM data - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - - // trigger should be gam since all adunits had associated gam render - expect(message.trigger).to.be.equal('gam'); - expect(message.auctions[0].adUnits[0].gam).to.deep.equal({ - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }); - expect(message.auctions[0].adUnits[1].gam).to.deep.equal({ - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }); - - // second one should NOT have gam data - request = server.requests[1]; - message = JSON.parse(request.requestBody); - validate(message); - - // trigger should be auctionEnd - expect(message.trigger).to.be.equal('auctionEnd'); - expect(message.auctions[0].adUnits[0].gam).to.be.undefined; - expect(message.auctions[0].adUnits[1].gam).to.be.undefined; - }); - - it('should send request when waitForGamSlots is present but no bidWons are sent', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.bidsWon; // should not be any of these - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expectedMessage.trigger = 'gam'; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should delay the event call depending on analyticsEventDelay config', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - analyticsEventDelay: 2000 - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - // Should not be sent until delay - expect(server.requests.length).to.equal(0); - - // tick the clock and it should fire - clock.tick(2000); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - let expectedGam0 = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - let expectedGam1 = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); - expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); - }); - }); - - it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID2); - seatBidResponse.seatBidId = 'abc-123-do-re-me'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('abc-123-do-re-me'); - expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); - }); - - it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - }); - - it('should correctly generate new bidId if it is 0', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - seatBidResponse.pbsBidId = '0'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); - expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); - }); - - it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let duplicateResponse1 = utils.deepClone(BID2); - duplicateResponse1.cpm = 1.0; - duplicateResponse1.adserverTargeting.hb_pb = '1.0'; - duplicateResponse1.adserverTargeting.hb_adid = '1111'; - let duplicateResponse2 = utils.deepClone(BID2); - duplicateResponse2.cpm = 5.5; - duplicateResponse2.adserverTargeting.hb_pb = '5.5'; - duplicateResponse2.adserverTargeting.hb_adid = '5555'; - let duplicateResponse3 = utils.deepClone(BID2); - duplicateResponse3.cpm = 0.1; - duplicateResponse3.adserverTargeting.hb_pb = '0.1'; - duplicateResponse3.adserverTargeting.hb_adid = '3333'; - - const setTargeting = { - [duplicateResponse2.adUnitCode]: duplicateResponse2.adserverTargeting - }; - - const bidWon = Object.assign({}, duplicateResponse2, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, duplicateResponse1); - events.emit(BID_RESPONSE, duplicateResponse2); - events.emit(BID_RESPONSE, duplicateResponse3); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_adid).to.equal('5555'); - expect(message.bidsWon.length).to.equal(1); - expect(message.bidsWon[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.bidsWon[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.bidsWon[0].adserverTargeting.hb_adid).to.equal('5555'); - }); - - it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(2); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message.auctions).to.deep.equal(ANALYTICS_MESSAGE.auctions); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[0]); - - message = JSON.parse(server.requests[1].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message).to.not.have.property('auctions'); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[1]); - }); - - it('should properly mark bids as timed out', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - let timedOutBid = message.auctions[0].adUnits[0].bids[0]; - expect(timedOutBid.status).to.equal('error'); - expect(timedOutBid.error.code).to.equal('timeout-error'); - expect(timedOutBid.error.description).to.equal('prebid.js timeout'); - expect(timedOutBid).to.not.have.property('bidResponse'); - }); - - it('should pass aupName as pattern', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' - } - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' - } - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); - expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); - }); - - it('should pass gpid if defined', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/lowerbox' - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/leaderboard' - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].gpid).to.equal('1234/mycoolsite/lowerbox'); - expect(message.auctions[0].adUnits[1].gpid).to.equal('1234/mycoolsite/leaderboard'); - }); - - it('should pass bidderDetail for multibid auctions', function () { - let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse.targetingBidder = 'rubi2'; - bidResponse.originalRequestId = bidResponse.requestId; - bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; - - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, bidResponse); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - - expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); - expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); - }); - - it('should successfully convert bid price to USD in parseBidResponse', function () { - // Set the rates - setConfig({ - adServerCurrency: 'JPY', - rates: { - USD: { - JPY: 100 - } - } - }); - - // set our bid response to JPY - const bidCopy = utils.deepClone(BID2); - bidCopy.currency = 'JPY'; - bidCopy.cpm = 100; - - // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response - let innerBid; - addBidResponseHook(function (adCodeId, bid) { - innerBid = bid; - }, 'elementId', bidCopy); - - // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! - const bidResponseObj = parseBidResponse(innerBid); - expect(bidResponseObj).to.have.property('bidPriceUSD'); - expect(bidResponseObj.bidPriceUSD).to.equal(1.0); - }); - }); - - describe('config with integration type', () => { - it('should use the integration type provided in the config instead of the default', () => { - config.setConfig({ - rubicon: { - int_type: 'testType' - } - }) - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.integration).to.equal('testType'); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - describe('billing events integration', () => { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - // default dmBilling - config.setConfig({ - rubicon: { - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - } - } - }) - }); - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - const basicBillingAuction = (billingEvents = []) => { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // emit billing events - billingEvents.forEach(ev => events.emit(BILLABLE_EVENT, ev)); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - } - it('should ignore billing events when not enabled', () => { - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events when enabled but vendor is not whitelisted', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events if billingId is not defined or billingId is not a string', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: true - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 1233434 - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: null - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should pass along billing event in same payload', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([{ - accountId: 1001, - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - }); - it('should pass along multiple billing events but filter out duplicates', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - accountId: 1001, - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - } - ]); - }); - it('should pass along event right away if no pending auction', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - - events.emit(BILLABLE_EVENT, { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.not.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - }); - it('should pass along event right away if pending auction but not waiting', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'], - waitForAuction: false - } - } - }); - // should fire right away, and then auction later - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(2); - const billingRequest = server.requests[0]; - const billingMessage = JSON.parse(billingRequest.requestBody); - expect(billingMessage).to.not.haveOwnProperty('auctions'); - expect(billingMessage.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - // auction event after - const auctionRequest = server.requests[1]; - const auctionMessage = JSON.parse(auctionRequest.requestBody); - // should not double pass events! - expect(auctionMessage).to.not.haveOwnProperty('billableEvents'); - }); - }); - - describe('wrapper details passed in', () => { - it('should correctly pass in the wrapper details if provided', () => { - config.setConfig({ - rubicon: { - wrapperName: '1001_wrapperName_exp.4', - wrapperFamily: '1001_wrapperName', - rule_name: 'na-mobile' - } - }); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.wrapper).to.deep.equal({ - name: '1001_wrapperName_exp.4', - family: '1001_wrapperName', - rule: 'na-mobile' - }); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { - let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); - inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); - inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; - expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); - - // not non-UTF char's in query / path which break if noDecodeWholeURL not set - inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - }); -}); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json deleted file mode 100644 index 2d0dca42d23..00000000000 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Prebid Auctions", - "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", - "type": "object", - "required": [ - "integration", - "version" - ], - "anyOf": [ - { - "required": [ - "auctions" - ] - }, - { - "required": [ - "bidsWon" - ] - }, - { - "required": [ - "billableEvents" - ] - } - ], - "properties": { - "integration": { - "type": "string", - "description": "Integration type that generated this event.", - "default": "pbjs" - }, - "version": { - "type": "string", - "description": "Version of Prebid.js responsible for the auctions contained within." - }, - "fpkvs": { - "type": "array", - "description": "List of any dynamic key value pairs set by publisher.", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - }, - "session": { - "type": "object", - "description": "The session information for a given event", - "required": [ - "id", - "start", - "expires" - ], - "properties": { - "id": { - "type": "string", - "description": "UUID of session." - }, - "start": { - "type": "integer", - "description": "Unix timestamp of time of creation for this session in milliseconds." - }, - "expires": { - "type": "integer", - "description": "Unix timestamp of the maximum allowed time in milliseconds of the session." - }, - "pvid": { - "type": "string", - "description": "id to track page view." - } - } - }, - "auctions": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "adUnits", - "samplingFactor" - ], - "properties": { - "clientTimeoutMillis": { - "type": "integer", - "description": "Timeout given in client for given auction in milliseconds (if applicable)." - }, - "serverTimeoutMillis": { - "type": "integer", - "description": "Timeout configured for server adapter request in milliseconds (if applicable)." - }, - "accountId": { - "type": "number", - "description": "The account id for prebid server (if applicable)." - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnits": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "description": "An array of adUnits involved in this auction.", - "required": [ - "status", - "adUnitCode", - "transactionId", - "mediaTypes", - "dimensions", - "bids" - ], - "properties": { - "status": { - "type": "string", - "description": "The status of the adUnit" - }, - "adUnitCode": { - "type": "string", - "description": "The adUnit.code identifier" - }, - "transactionId": { - "type": "string", - "description": "The UUID generated id to represent this adunit in this auction." - }, - "adSlot": { - "type": "string" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "dimensions": { - "type": "array", - "description": "All valid sizes included in this auction (note: may be sizeConfig filtered).", - "minItems": 1, - "items": { - "$ref": "#/definitions/dimensions" - } - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bids": { - "type": "array", - "description": "An array that contains a combination of the bids from the adUnit combined with their responses.", - "minItems": 1, - "items": { - "$ref": "#/definitions/bid" - } - }, - "accountId": { - "type": "number", - "description": "The Rubicon AccountId associated with this adUnit - Removed if null" - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - }, - "gam": { - "$ref": "#/definitions/gam" - } - } - } - } - } - } - }, - "bidsWon": { - "type": "array", - "minItems": 1, - "items": { - "allOf": [ - { - "$ref": "#/definitions/bid" - }, - { - "required": [ - "transactionId", - "accountId", - "samplingFactor", - "mediaTypes", - "adUnitCode", - "bidwonStatus" - ], - "properties": { - "transactionId": { - "type": "string" - }, - "accountId": { - "type": "number" - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnitCode": { - "type": "string" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bidwonStatus": { - "description": "Whether the bid was successfully rendered or not", - "type": "string", - "enum": [ - "success", - "error" - ] - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - } - } - } - ] - } - }, - "billableEvents":{ - "type":"array", - "minItems":1, - "items":{ - "type":"object", - "required":[ - "accountId", - "vendor", - "type", - "billingId" - ], - "properties":{ - "vendor":{ - "type":"string", - "description":"The name of the vendor who emitted the billable event" - }, - "type":{ - "type":"string", - "description":"The type of billable event", - "enum":[ - "impression", - "pageLoad", - "auction", - "request", - "general" - ] - }, - "billingId":{ - "type":"string", - "description":"A UUID which is responsible more mapping this event to" - }, - "accountId": { - "type": "number", - "description": "The account id for the rubicon publisher" - } - } - } - } - }, - "definitions": { - "gam": { - "type": "object", - "description": "The gam information for a given ad unit", - "required": [ - "adSlot" - ], - "properties": { - "adSlot": { - "type": "string" - }, - "advertiserId": { - "type": "integer" - }, - "creativeId": { - "type": "integer" - }, - "LineItemId": { - "type": "integer" - }, - "isSlotEmpty": { - "type": "boolean", - "enum": [ - true - ] - } - } - }, - "adserverTargeting": { - "type": "object", - "description": "The adserverTargeting key/value pairs", - "patternProperties": { - ".+": { - "type": "string" - } - } - }, - "samplingFactor": { - "type": "integer", - "description": "An integer value representing the factor to multiply event count by to receive unsampled count.", - "enum": [ - 1, - 10, - 20, - 40, - 100 - ] - }, - "videoAdFormat": { - "type": "string", - "description": "This value only provided for video specifies the ad format", - "enum": [ - "pre-roll", - "interstitial", - "outstream", - "mid-roll", - "post-roll", - "vertical" - ] - }, - "mediaTypes": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - } - }, - "dimensions": { - "type": "object", - "description": "Size object representing the dimensions of creative in pixels.", - "required": [ - "width", - "height" - ], - "properties": { - "width": { - "type": "integer", - "minimum": 1 - }, - "height": { - "type": "integer", - "minimum": 1 - } - } - }, - "bid": { - "type": "object", - "required": [ - "bidder", - "bidId", - "status", - "source" - ], - "properties": { - "bidder": { - "type": "string" - }, - "bidId": { - "type": "string", - "description": "UUID representing this individual bid request in this auction." - }, - "params": { - "description": "A copy of the bid.params from the adUnit.bids", - "anyOf": [ - { - "type": "object" - }, - { - "$ref": "#/definitions/params/rubicon" - } - ] - }, - "status": { - "type": "string", - "enum": [ - "success", - "no-bid", - "error", - "rejected-gdpr", - "rejected-ipf" - ] - }, - "error": { - "type": "object", - "additionalProperties": false, - "required": [ - "code" - ], - "properties": { - "code": { - "type": "string", - "enum": [ - "request-error", - "connect-error", - "timeout-error" - ] - }, - "description": { - "type": "string" - } - } - }, - "source": { - "type": "string", - "enum": [ - "client", - "server" - ] - }, - "clientLatencyMillis": { - "type": "integer", - "description": "Latency from auction start to bid response recieved in milliseconds." - }, - "serverLatencyMillis": { - "type": "integer", - "description": "Latency returned by prebid server (response_time_ms)." - }, - "bidResponse": { - "type": "object", - "required": [ - "mediaType", - "bidPriceUSD" - ], - "properties": { - "dimensions": { - "$ref": "#/definitions/dimensions" - }, - "mediaType": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - }, - "bidPriceUSD": { - "type": "number", - "description": "The bid value denoted in USD" - }, - "dealId": { - "type": "integer", - "description": "The id associated with any potential deals" - } - } - } - } - }, - "params": { - "rubicon": { - "type": "object", - "properties": { - "accountId": { - "type": "number" - }, - "siteId": { - "type": "number" - }, - "zoneId": { - "type": "number" - } - } - } - } - } -}