From ab4741db894cee4f2c08c6a28467c9bd753b6613 Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Fri, 14 Apr 2023 15:41:35 -0400 Subject: [PATCH] Kargo Bid Adapter: Refactor of bid request (#9731) * pageURL pull from topmostLocation * Kargo: Support for client hints (#9) * Starting SUA support * Kargo: Adding support for client hints * Adding tests for sua * Kargo: Update referer logic * Refactor of Kargo Prebid adapter. * PR comments addressed. * Feedback addressed. * Pr comments addressed. * Continuing refactor of Kargo Bid adapter. * Logic adjustment to exclude values when not present. Relying on server defaults. * Updating unit tests. * PR feedback addressed. * Refactoring bid adapter functions. * PR feedback addressed. * Additional refactoring. * Refactoring for each to use Object entries. * Minor fixes. * Minor fixes. * Minor fixes. * TDID and linting updates * Conflicts resolved with master. * Re-adding raw CRB storage (#14) * Updating shared IDs object name * Fixing missing ad markup * Removing package json changes. Fixing unit tests broken by recent changes. * Linting * send requestCount even when it is 0 for BTO (#18) * Reverting package.json change * Reverting package-lock.json changes * Cleanup * Test cleanup * Test fix Test fix All tests fixed * Adding test for TDID * Resolving merge issue --------- Co-authored-by: Neil Flynn Co-authored-by: Julian Gan --- modules/kargoBidAdapter.js | 705 ++++++++++++++-------- test/spec/modules/kargoBidAdapter_spec.js | 422 +++++++++---- 2 files changed, 745 insertions(+), 382 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 0c647095e24..ecc40b26aa2 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,295 +1,504 @@ -import { _each, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'kargo'; -const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; -const PREBID_VERSION = '$prebid.version$'; -const SYNC_COUNT = 5; -const GVLID = 972; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const PREBID_VERSION = '$prebid.version$' + +const BIDDER = Object.freeze({ + CODE: 'kargo', + HOST: 'krk2.kargo.com', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/prebid', + TIMEOUT_ENDPOINT: '/api/v1/event/timeout', + GVLID: 972, + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO], +}); + +const STORAGE = getStorageManager({bidderCode: BIDDER.CODE}); + +const CURRENCY = Object.freeze({ + KEY: 'currency', + US_DOLLAR: 'USD', +}); + +const REQUEST_KEYS = Object.freeze({ + SOCIAL_CANVAS: 'params.socialCanvas', + SUA: 'ortb2.device.sua', + TDID_ADAPTER: 'userId.tdid', +}); + +const SUA = Object.freeze({ + BROWSERS: 'browsers', + MOBILE: 'mobile', + MODEL: 'model', + PLATFORM: 'platform', + SOURCE: 'source', +}); + +const SUA_ATTRIBUTES = [ + SUA.BROWSERS, + SUA.MOBILE, + SUA.MODEL, + SUA.SOURCE, + SUA.PLATFORM, +]; + +const CERBERUS = Object.freeze({ + KEY: 'krg_crb', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}', + SYNC_COUNT: 5, + PAGE_VIEW_ID: 'pageViewId', + PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', + PAGE_VIEW_URL: 'pageViewUrl' +}); let sessionId, lastPageUrl, requestCounter; -export const spec = { - gvlid: GVLID, - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - if (!bid || !bid.params) { - return false; - } +function isBidRequestValid(bid) { + if (!bid || !bid.params) { + return false; + } - return !!bid.params.placementId; - }, - buildRequests: function(validBidRequests, bidderRequest) { - const currencyObj = config.getConfig('currency'); - const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; - const bidIDs = {}; - const bidSizes = {}; - - _each(validBidRequests, bid => { - bidIDs[bid.bidId] = bid.params.placementId; - bidSizes[bid.bidId] = bid.sizes; - }); + return !!bid.params.placementId; +} + +function buildRequests(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig(CURRENCY.KEY); + const currency = (currencyObj && currencyObj.adServerCurrency) ? currencyObj.adServerCurrency : null; + const impressions = []; + + _each(validBidRequests, bid => { + impressions.push(getImpression(bid)) + }); + + const firstBidRequest = validBidRequests[0]; + const tdidAdapter = deepAccess(firstBidRequest, REQUEST_KEYS.TDID_ADAPTER); + + const metadata = getAllMetadata(bidderRequest); + + const krakenParams = Object.assign({}, { + pbv: PREBID_VERSION, + aid: firstBidRequest.auctionId, + sid: _getSessionId(), + url: metadata.pageURL, + timeout: bidderRequest.timeout, + ts: new Date().getTime(), + device: { + size: [ + window.screen.width, + window.screen.height + ] + }, + imp: impressions, + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids), + }); + + const reqCount = getRequestCount() + if (reqCount != null) { + krakenParams.requestCount = reqCount; + } - const firstBidRequest = validBidRequests[0]; - - const tdid = deepAccess(firstBidRequest, 'userId.tdid') - - const transformedParams = Object.assign({}, { - sessionId: spec._getSessionId(), - requestCount: spec._getRequestCount(), - timeout: bidderRequest.timeout, - currency, - cpmGranularity: 1, - timestamp: (new Date()).getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs, - bidSizes, - device: { - width: window.screen.width, - height: window.screen.height - }, - prebidRawBidRequests: validBidRequests, - prebidVersion: PREBID_VERSION - }, spec._getAllMetadata(bidderRequest, tdid)); - - // User Agent Client Hints / SUA - const uaClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); - if (uaClientHints) { - transformedParams.device.sua = pick(uaClientHints, ['browsers', 'platform', 'mobile', 'model']); - } + if (currency != null && currency != CURRENCY.US_DOLLAR) { + krakenParams.cur = currency; + } - // Pull Social Canvas segments and embed URL - const socialCanvas = deepAccess(firstBidRequest, 'params.socialCanvas'); - if (socialCanvas) { - transformedParams.socialCanvasSegments = socialCanvas.segments; - transformedParams.socialEmbedURL = socialCanvas.embedURL; - } + if (metadata.rawCRB != null) { + krakenParams.rawCRB = metadata.rawCRB + } - const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); - return Object.assign({}, bidderRequest, { - method: 'GET', - url: `${HOST}/api/v2/bid`, - data: `json=${encodedParams}`, - currency: currency - }); - }, - interpretResponse: function(response, bidRequest) { - let bids = response.body; - const bidResponses = []; - for (let bidId in bids) { - let adUnit = bids[bidId]; - let meta = { - mediaType: BANNER - }; - - if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta.clickUrl = adUnit.metadata.landingPageDomain[0]; - meta.advertiserDomains = adUnit.metadata.landingPageDomain; + if (metadata.rawCRBLocalStorage != null) { + krakenParams.rawCRBLocalStorage = metadata.rawCRBLocalStorage + } + + // Pull Social Canvas segments and embed URL + const socialCanvas = deepAccess(firstBidRequest, REQUEST_KEYS.SOCIAL_CANVAS); + + if (socialCanvas != null) { + krakenParams.socan = socialCanvas; + } + + // User Agent Client Hints / SUA + const uaClientHints = deepAccess(firstBidRequest, REQUEST_KEYS.SUA); + if (uaClientHints) { + const suaValidAttributes = [] + + SUA_ATTRIBUTES.forEach(suaKey => { + const suaValue = uaClientHints[suaKey]; + if (!suaValue) { + return; } - if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { - meta.mediaType = adUnit.mediaType; + // Do not pass any empty strings + if (typeof suaValue == 'string' && suaValue.trim() === '') { + return; } - const bidResponse = { - ad: adUnit.adm, - requestId: bidId, - cpm: Number(adUnit.cpm), - width: adUnit.width, - height: adUnit.height, - ttl: 300, - creativeId: adUnit.id, - dealId: adUnit.targetingCustom, - netRevenue: true, - currency: adUnit.currency || bidRequest.currency, - mediaType: meta.mediaType, - meta: meta - }; - - if (meta.mediaType == VIDEO) { - if (adUnit.admUrl) { - bidResponse.vastUrl = adUnit.admUrl; - } else { - bidResponse.vastXml = adUnit.adm; - } + switch (suaKey) { + case SUA.MOBILE && suaValue < 1: // Do not pass 0 value for mobile + case SUA.SOURCE && suaValue < 1: // Do not pass 0 value for source + break; + default: + suaValidAttributes.push(suaKey); } + }); - bidResponses.push(bidResponse); - } + krakenParams.device.sua = pick(uaClientHints, suaValidAttributes); + } + + const validPageId = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID) != null + const validPageTimestamp = getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP) != null + const validPageUrl = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL) != null + + const page = {} + if (validPageId) { + page.id = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID); + } + if (validPageTimestamp) { + page.timestamp = Number(getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP)); + } + if (validPageUrl) { + page.url = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL); + } + if (!isEmpty(page)) { + krakenParams.page = page; + } + + return Object.assign({}, bidderRequest, { + method: BIDDER.REQUEST_METHOD, + url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, + data: krakenParams, + currency: currency + }); +} + +function interpretResponse(response, bidRequest) { + let bids = response.body; + const bidResponses = []; + if (isEmpty(bids)) { return bidResponses; - }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - const syncs = []; - const seed = spec._generateRandomUuid(); - const clientId = spec._getClientId(); - var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; - var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; - // don't sync if opted out via usPrivacy - if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { - return syncs; + } + + if (typeof bids !== 'object') { + return bidResponses; + } + + Object.entries(bids).forEach((entry) => { + const [bidID, adUnit] = entry; + + let meta = { + mediaType: adUnit.mediaType && BIDDER.SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType) ? adUnit.mediaType : BANNER + }; + + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; } - if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - }); + + const bidResponse = { + requestId: bidID, + cpm: Number(adUnit.cpm), + width: adUnit.width, + height: adUnit.height, + ttl: 300, + creativeId: adUnit.id, + dealId: adUnit.targetingCustom, + netRevenue: true, + currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, + meta: meta + }; + + if (meta.mediaType == VIDEO) { + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; } + } else { + bidResponse.ad = adUnit.adm; } + + bidResponses.push(bidResponse); + }) + + return bidResponses; +} + +function getUserSyncs(syncOptions, responses, gdprConsent, usPrivacy) { + const syncs = []; + const seed = _generateRandomUUID(); + const clientId = getClientId(); + + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { return syncs; - }, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - onTimeout: function(timeoutData) { - if (timeoutData == null) { - return; + } + if (syncOptions.iframeEnabled && seed && clientId) { + for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + }); } + } + return syncs; +} - timeoutData.forEach((bid) => { - this._sendTimeoutData(bid.auctionId, bid.timeout); - }); - }, - - _getCrbFromCookie() { - try { - const crb = JSON.parse(storage.getCookie('krg_crb')); - if (crb && crb.v) { - let vParsed = JSON.parse(atob(crb.v)); - if (vParsed) { - return vParsed; - } +function onTimeout(timeoutData) { + if (timeoutData == null) { + return; + } + + timeoutData.forEach((bid) => { + sendTimeoutData(bid.auctionId, bid.timeout); + }); +} + +function _generateRandomUUID() { + try { + // crypto.getRandomValues is supported everywhere but Opera Mini for years + var buffer = new Uint8Array(16); + crypto.getRandomValues(buffer); + buffer[6] = (buffer[6] & ~176) | 64; + buffer[8] = (buffer[8] & ~64) | 128; + var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + return ('00' + x.toString(16)).slice(-2); + }).join(''); + return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); + } catch (e) { + return ''; + } +} + +function _getCrb() { + let localStorageCrb = getCrbFromLocalStorage(); + if (Object.keys(localStorageCrb).length) { + return localStorageCrb; + } + return getCrbFromCookie(); +} + +function _getSessionId() { + if (!sessionId) { + sessionId = _generateRandomUUID(); + } + return sessionId; +} + +function getCrbFromCookie() { + try { + const crb = JSON.parse(STORAGE.getCookie(CERBERUS.KEY)); + if (crb && crb.v) { + let vParsed = JSON.parse(atob(crb.v)); + if (vParsed) { + return vParsed; } - return {}; - } catch (e) { - return {}; } - }, + return {}; + } catch (e) { + return {}; + } +} - _getCrbFromLocalStorage() { - try { - return JSON.parse(atob(spec._getLocalStorageSafely('krg_crb'))); - } catch (e) { - return {}; - } - }, +function getCrbFromLocalStorage() { + try { + return JSON.parse(atob(getLocalStorageSafely(CERBERUS.KEY))); + } catch (e) { + return {}; + } +} - _getCrb() { - let localStorageCrb = spec._getCrbFromLocalStorage(); - if (Object.keys(localStorageCrb).length) { - return localStorageCrb; - } - return spec._getCrbFromCookie(); - }, - - _getLocalStorageSafely(key) { - try { - return storage.getDataFromLocalStorage(key); - } catch (e) { - return null; - } - }, - - _getUserIds(tdid, usp, gdpr) { - const crb = spec._getCrb(); - const userIds = { - kargoID: crb.lexId, - clientID: crb.clientId, - crbIDs: crb.syncIds || {}, - optOut: crb.optOut, - usp: usp - }; +function getLocalStorageSafely(key) { + try { + return STORAGE.getDataFromLocalStorage(key); + } catch (e) { + return null; + } +} + +function getUserIds(tdidAdapter, usp, gdpr, eids) { + const crb = spec._getCrb(); + const userIds = { + crbIDs: crb.syncIds || {} + }; + + // Pull Trade Desk ID from adapter + if (tdidAdapter) { + userIds.tdID = tdidAdapter; + } + + // Pull Trade Desk ID from our storage + if (!tdidAdapter && crb.tdID) { + userIds.tdID = crb.tdID; + } + + if (usp) { + userIds.usp = usp; + } - try { - if (gdpr) { - userIds['gdpr'] = { - consent: gdpr.consentString || '', - applies: !!gdpr.gdprApplies, - } + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, } - } catch (e) { } - if (tdid) { - userIds.tdID = tdid; + } catch (e) { + } + + if (crb.lexId != null) { + userIds.kargoID = crb.lexId; + } + + if (crb.clientId != null) { + userIds.clientID = crb.clientId; + } + + if (crb.optOut != null) { + userIds.optOut = crb.optOut; + } + + if (eids != null) { + userIds.sharedIDEids = eids; + } + + return userIds; +} + +function getClientId() { + const crb = spec._getCrb(); + return crb.clientId; +} + +function getAllMetadata(bidderRequest) { + return { + pageURL: bidderRequest?.refererInfo?.page, + rawCRB: STORAGE.getCookie(CERBERUS.KEY), + rawCRBLocalStorage: getLocalStorageSafely(CERBERUS.KEY) + }; +} + +function getRequestCount() { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return requestCounter = 0; +} + +function sendTimeoutData(auctionId, auctionTimeout) { + let params = { + aid: auctionId, + ato: auctionTimeout + }; + + try { + let timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: BIDDER.HOST, + pathname: BIDDER.TIMEOUT_ENDPOINT, + search: params + }); + + triggerPixel(timeoutRequestUrl); + } catch (e) {} +} + +function getImpression(bid) { + const imp = { + id: bid.bidId, + tid: bid.transactionId, + pid: bid.params.placementId, + code: bid.adUnitCode + }; + + if (bid.floorData != null && bid.floorData.floorMin > 0) { + imp.floor = bid.floorData.floorMin; + } + + if (bid.bidRequestsCount > 0) { + imp.bidRequestCount = bid.bidRequestsCount; + } + + if (bid.bidderRequestsCount > 0) { + imp.bidderRequestCount = bid.bidderRequestsCount; + } + + if (bid.bidderWinsCount > 0) { + imp.bidderWinCount = bid.bidderWinsCount; + } + + const gpid = getGPID(bid) + if (gpid != null && gpid != '') { + imp.fpd = { + gpid: gpid + } + } + + if (bid.mediaTypes != null) { + if (bid.mediaTypes.banner != null) { + imp.banner = bid.mediaTypes.banner; } - return userIds; - }, - - _getClientId() { - const crb = spec._getCrb(); - return crb.clientId; - }, - - _getAllMetadata(bidderRequest, tdid) { - return { - userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), - pageURL: bidderRequest?.refererInfo?.page, - rawCRB: storage.getCookie('krg_crb'), - rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') - }; - }, - _getSessionId() { - if (!sessionId) { - sessionId = spec._generateRandomUuid(); + if (bid.mediaTypes.video != null) { + imp.video = bid.mediaTypes.video; } - return sessionId; - }, - _getRequestCount() { - if (lastPageUrl === window.location.pathname) { - return ++requestCounter; + if (bid.mediaTypes.native != null) { + imp.native = bid.mediaTypes.native; } - lastPageUrl = window.location.pathname; - return requestCounter = 0; - }, - - _generateRandomUuid() { - try { - // crypto.getRandomValues is supported everywhere but Opera Mini for years - var buffer = new Uint8Array(16); - crypto.getRandomValues(buffer); - buffer[6] = (buffer[6] & ~176) | 64; - buffer[8] = (buffer[8] & ~64) | 128; - var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { - return ('00' + x.toString(16)).slice(-2); - }).join(''); - return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); - } catch (e) { - return ''; + } + + return imp +} + +function getGPID(bid) { + if (bid.ortb2Imp != null) { + if (bid.ortb2Imp.gpid != null && bid.ortb2Imp.gpid != '') { + return bid.ortb2Imp.gpid; } - }, - _sendTimeoutData(auctionId, auctionTimeout) { - let params = { - aid: auctionId, - ato: auctionTimeout, - }; + if (bid.ortb2Imp.ext != null && bid.ortb2Imp.ext.data != null) { + if (bid.ortb2Imp.ext.data.pbAdSlot != null && bid.ortb2Imp.ext.data.pbAdSlot != '') { + return bid.ortb2Imp.ext.data.pbAdSlot; + } - try { - let timeoutRequestUrl = buildUrl({ - protocol: 'https', - hostname: 'krk.kargo.com', - pathname: '/api/v1/event/timeout', - search: params - }); + if (bid.ortb2Imp.ext.data.adServer != null && bid.ortb2Imp.ext.data.adServer.adSlot != null && bid.ortb2Imp.ext.data.adServer.adSlot != '') { + return bid.ortb2Imp.ext.data.adServer.adSlot; + } + } + } - triggerPixel(timeoutRequestUrl); - } catch (e) {} + if (bid.adUnitCode != null && bid.adUnitCode != '') { + return bid.adUnitCode; } + return ''; +} + +export const spec = { + gvlid: BIDDER.GVLID, + code: BIDDER.CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES, + onTimeout, + _getCrb, + _getSessionId }; + registerBidder(spec); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 556e06268b1..260ac6c2132 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('kargo adapter tests', function () { }); describe('build request', function() { - var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -45,6 +45,7 @@ describe('kargo adapter tests', function () { }; undefinedCurrency = false; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { if (key === 'currency') { if (undefinedCurrency) { @@ -53,6 +54,9 @@ describe('kargo adapter tests', function () { if (noAdServerCurrency) { return {}; } + if (nonUSDAdServerCurrency) { + return {adServerCurrency: 'EUR'}; + } return {adServerCurrency: 'USD'}; } if (key === 'debug') return true; @@ -63,13 +67,45 @@ describe('kargo adapter tests', function () { bids = [ { params: { - placementId: 'foo' + placementId: 'foo', + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } }, - bidId: 1, + auctionId: '1234098', + bidId: '1', + adUnitCode: '101', + transactionId: '10101', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 1, + bidderRequestsCount: 2, + bidderWinsCount: 3, userId: { - tdid: 'fake-tdid' + tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + }, + userIdAsEids: [ + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ], + floorData: { + floorMin: 1 }, - sizes: [[320, 50], [300, 250], [300, 600]], ortb2: { device: { sua: { @@ -91,25 +127,76 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '' + mobile: 1, + model: 'model', + source: 1, } } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } }, { params: { placementId: 'bar' }, - bidId: 2, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '2', + adUnitCode: '202', + transactionId: '20202', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + video: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 0, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } }, { params: { placementId: 'bar' }, - bidId: 3, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '3', + adUnitCode: '303', + transactionId: '30303', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + native: { + sizes: [[320, 50], [300, 50]] + } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } } ]; }); @@ -160,11 +247,19 @@ describe('kargo adapter tests', function () { function simulateNoCurrencyObject() { undefinedCurrency = true; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; } function simulateNoAdServerCurrency() { undefinedCurrency = false; noAdServerCurrency = true; + nonUSDAdServerCurrency = false; + } + + function simulateNonUSDAdServerCurrency() { + undefinedCurrency = false; + noAdServerCurrency = false; + nonUSDAdServerCurrency = true; } function generateGDPR(applies, haveConsent) { @@ -182,6 +277,32 @@ describe('kargo adapter tests', function () { }; } + function generatePageView() { + return { + id: '112233', + timestamp: frozenNow.getTime(), + url: 'http://pageview.url' + } + } + + function generateRawCRB(rawCRB, rawCRBLocalStorage) { + if (rawCRB == null && rawCRBLocalStorage == null) { + return null + } + + let result = {} + + if (rawCRB != null) { + result.rawCRB = rawCRB + } + + if (rawCRBLocalStorage != null) { + result.rawCRBLocalStorage = rawCRBLocalStorage + } + + return result + } + function getKrgCrb() { return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; } @@ -253,6 +374,12 @@ describe('kargo adapter tests', function () { setLocalStorageItem('krg_crb', getEmptyKrgCrb()); } + function initializePageView() { + setLocalStorageItem('pageViewId', 112233); + setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); + setLocalStorageItem('pageViewUrl', 'http://pageview.url'); + } + function initializeEmptyKrgCrbCookie() { setCookie('krg_crb', getEmptyKrgCrbOldStyle()); } @@ -261,30 +388,20 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { + function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { var base = { + pbv: '$prebid.version$', + aid: '1234098', + requestCount: 0, + sid: getSessionId(), + url: 'https://www.prebid.org', timeout: 200, - requestCount: requestCount++, - currency: 'USD', - cpmGranularity: 1, - timestamp: frozenNow.getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs: { - 1: 'foo', - 2: 'bar', - 3: 'bar' - }, - bidSizes: { - 1: [[320, 50], [300, 250], [300, 600]], - 2: [[320, 50], [300, 250], [300, 600]], - 3: [[320, 50], [300, 250], [300, 600]] - }, + ts: frozenNow.getTime(), device: { - width: screen.width, - height: screen.height, + size: [ + screen.width, + screen.height + ], sua: { platform: { brand: 'macOS', @@ -304,14 +421,61 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '', + mobile: 1, + model: 'model', + source: 1 }, }, - userIDs: { + imp: [ + { + code: '101', + id: '1', + pid: 'foo', + tid: '10101', + banner: { + sizes: [[320, 50], [300, 50]] + }, + bidRequestCount: 1, + bidderRequestCount: 2, + bidderWinCount: 3, + floor: 1, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '202', + id: '2', + pid: 'bar', + tid: '20202', + video: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '303', + id: '3', + pid: 'bar', + tid: '30303', + native: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + ], + socan: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + }, + user: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'fake-tdid', + tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', crbIDs: { 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', @@ -322,86 +486,63 @@ describe('kargo adapter tests', function () { '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' }, optOut: false, - usp: '1---' - }, - pageURL: 'https://www.prebid.org', - prebidVersion: '$prebid.version$', - prebidRawBidRequests: [ - { - bidId: 1, - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: [ '12', '6', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - } - ], - mobile: 0, - model: '' + usp: '1---', + sharedIDEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + atype: 1, + ext: { + rtiPartner: 'TDID' + } } - } - }, - params: { - placementId: 'foo' - }, - userId: { - tdid: 'fake-tdid' - }, - sizes: [[320, 50], [300, 250], [300, 600]], - }, - { - bidId: 2, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - }, - { - bidId: 3, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - } - ], - rawCRB: expectedRawCRBCookie, - rawCRBLocalStorage: expectedRawCRB + ] + } + ] + } }; + if (excludeUserIds) { + base.user.crbIDs = {}; + delete base.user.clientID; + delete base.user.kargoID; + delete base.user.optOut; + } + if (expectedGDPR) { - base.userIDs['gdpr'] = expectedGDPR; + base.user.gdpr = expectedGDPR; + } + + if (expectedPage) { + base.page = expectedPage; } - if (excludeUserIds === true) { - base.userIDs = { - crbIDs: {}, - usp: '1---' - }; - delete base.prebidRawBidRequests[0].userId.tdid; + if (currency) { + base.cur = currency; + } + + const reqCount = requestCount++; + if (reqCount > 0) { + base.requestCount = reqCount + } + + if (expectedCRB != null) { + if (expectedCRB.rawCRB != null) { + base.rawCRB = expectedCRB.rawCRB + } + if (expectedCRB.rawCRBLocalStorage != null) { + base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage + } } return base; } - function testBuildRequests(excludeTdid, expected, gdpr) { + function testBuildRequests(expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); - if (excludeTdid) { - delete clonedBids[0].userId.tdid; - } + var payload = { timeout: 200, uspConsent: '1---', @@ -415,15 +556,13 @@ describe('kargo adapter tests', function () { } var request = spec.buildRequests(clonedBids, payload); - expected.sessionId = getSessionId(); - sessionIds.push(expected.sessionId); - var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); - expect(request.data.slice(0, 5)).to.equal('json='); - expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid'); - expect(request.method).to.equal('GET'); - expect(request.currency).to.equal('USD'); + var krakenParams = request.data; + + expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); + expect(request.method).to.equal('POST'); expect(request.timeout).to.equal(200); expect(krakenParams).to.deep.equal(expected); + // Make sure session ID stays the same across requests simulating multiple auctions on one page load for (let i in sessionIds) { if (i == 0) { @@ -436,76 +575,93 @@ describe('kargo adapter tests', function () { it('works when all params and localstorage and cookies are correctly set', function() { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('works when all params and cookies are correctly set but no localstorage', function() { initializeKrgCrb(true); - testBuildRequests(false, getExpectedKrakenParams(undefined, null, getKrgCrbOldStyle())); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); }); it('gracefully handles nothing being set', function() { - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('handles empty yet valid Kargo CRB', function() { initializeEmptyKrgCrb(); initializeEmptyKrgCrbCookie(); - testBuildRequests(true, getExpectedKrakenParams(true, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); }); it('handles broken Kargo CRBs where base64 encoding is invalid', function() { initializeInvalidKrgCrbType1(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType1(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { initializeInvalidKrgCrbType1Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType1())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where decoded JSON is invalid', function() { initializeInvalidKrgCrbType2(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType2(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { initializeInvalidKrgCrbType2Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType2OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { initializeInvalidKrgCrbType3Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType3OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is falsey', function() { initializeInvalidKrgCrbType4Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType4OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); }); it('handles a non-existant currency object on the config', function() { simulateNoCurrencyObject(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('handles no ad server currency being set on the currency object in the config', function() { simulateNoAdServerCurrency(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); + }); + + it('handles non-USD ad server currency being set on the currency object in the config', function() { + simulateNonUSDAdServerCurrency(); + initializeKrgCrb(); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); }); it('sends gdpr consent', function () { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); }); }); @@ -604,11 +760,11 @@ describe('kargo adapter tests', function () { }] }); var expectation = [{ + ad: '
', requestId: '1', cpm: 3, width: 320, height: 50, - ad: '
', ttl: 300, creativeId: 'foo', dealId: undefined, @@ -620,10 +776,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '2', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: 'dmpmptest1234', @@ -637,10 +793,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '3', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -652,10 +808,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '4', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -670,7 +826,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -686,7 +841,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastUrl: 'https://foobar.com/vast_adm', ttl: 300, creativeId: 'bar', @@ -831,7 +985,7 @@ describe('kargo adapter tests', function () { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk.kargo.com/api/v1/event/timeout'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); });