diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index e85ac8e1337..b4e7495002e 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,6 +68,7 @@ } }, realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -76,9 +77,10 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.0.0", - segtax: 503, - tokenTtl: 5, + identityType: "dap-signature:1.3.0", + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 } } ] diff --git a/modules/.submodules.json b/modules/.submodules.json index 9402d0561fa..8d62f30d7c4 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -52,6 +52,7 @@ ], "rtdModule": [ "airgridRtdProvider", + "akamaiDapRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 1091b87a22d..095fb917597 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -63,15 +63,15 @@ MACRO['pageurl'] = function(b, c) { const refererInfo = getRefererInfo(); return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0]; }; -MACRO['pbadslot'] = function(b, c) { +MACRO['gpid'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); - return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; + return deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; -MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy +MACRO['pbAdSlot'] = MACRO['pbadslot'] = MACRO['gpid']; // legacy const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, - 'id2': '%%pbadslot%%', + 'id2': '%%gpid%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, @@ -138,7 +138,11 @@ analyticsAdapter.enableAnalytics = function(config) { toselector: config.options.toselector || function(bid) { let code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes - code = code.replace(/^\d/, '\\3$& '); + try { + code = CSS.escape(code); + } catch (_) { + code = code.replace(/^\d/, '\\3$& '); + } return `#${code}` }, client: config.options.client, diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index e21261d0b8d..203b118652e 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -106,7 +106,7 @@ For example, you have a number of reporting breakdown slots available in the for tagid: 0, params: { id1: function(b) { return b.adUnitCode }, // do not change when using the Adloox RTD Provider - id2: '%%pbadslot%%', // do not change when using the Adloox RTD Provider + id2: '%%gpid%%', // do not change when using the Adloox RTD Provider id3: function(b) { return b.bidder }, id4: function(b) { return b.adId }, id5: function(b) { return b.dealId }, @@ -125,9 +125,9 @@ For example, you have a number of reporting breakdown slots available in the for The following macros are available - * `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) + * **`%%gpid%%` (alias `%%pbadslot%%`**): [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) * it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot) - * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` + * **`%%pageurl%%`**: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` ### Functions diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index bb8334ec8fe..489fadf91f4 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -216,13 +216,11 @@ function init(config, userConsent) { function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - let path = deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot'); - if (!path) path = getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot; return { - path: path, + gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, unit: adUnit }; - }).filter(adUnit => !!adUnit.path); + }).filter(adUnit => !!adUnit.gpid); let response = {}; function setSegments() { @@ -315,7 +313,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { } const atfQueue = []; adUnits.map((adUnit, i) => { - const ref = [ adUnit.path ]; + const ref = [ adUnit.gpid ]; if (!config.params.slotinpath) ref.push(adUnit.unit.code); args.push(['s', ref.join('\t')]); diff --git a/modules/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 6c75fbc2d8b..466f8ed1ba2 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -81,7 +81,7 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn You may optionally pass a subsection `params` in the `params` block to the Adloox RTD Provider, these will be passed through to the segment handler as is and as described by the integration guidelines. -**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%pbadslot%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing +**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%gpid%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing Though our segment technology is fast (less than 10ms) the time it takes for the users device to connect to our service and fetch the segments may not be. For this reason we recommend setting `auctionDelay` no lower than 100ms and if possible you should explore using user-agent sourced information such as [NetworkInformation.{rtt,downlink,...}](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) to dynamically tune this for each user. @@ -94,7 +94,7 @@ You may use one of two ways to do achieve this: * for display inventory [using GPT](https://developers.google.com/publisher-tag/guides/get-started) you may configure Prebid.js to automatically use the [full ad unit path](https://developers.google.com/publisher-tag/reference#googletag.Slot_getAdUnitPath) 1. include the [`gptPreAuction` module](https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html) 1. wrap both `pbjs.setConfig({...})` and `pbjs.enableAnalytics({...})` with `googletag.cmd.push(function() { ... })` - * set `pbadslot` in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.data.pbadslot` for all your ad units + * set `gpid` (or `pbadslot`) in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.gpid` (or `AdUnit.ortb2Imp.ext.data.pbadslot`) for all your ad units ## Timeouts diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index aca984d39c8..27d8516f3b9 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,12 +10,22 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; +const MODULE_CODE = 'akamaidap'; + +export const DAP_TOKEN = 'async_dap_token'; +export const DAP_MEMBERSHIP = 'async_dap_membership'; +export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; +export const DAP_SS_ID = 'dap_ss_id'; +export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds +export const DAP_MAX_RETRY_TOKENIZE = 1; +export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -53,60 +63,92 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfi + * @param {Object} rtdConfig * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - logInfo('DEBUG(getRealTimeData) - ENTER'); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapFpTimeout && Number.isInteger(rtdConfig.params.dapFpTimeout)) { + setTimeout(reject, rtdConfig.params.dapFpTimeout, Error('DapFP script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); + } else { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapFpUrl)) { + loadExternalScript(rtdConfig.params.dapFpUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapFpUrl is specified and is valid under config.params')); + } + } + } + }); + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); +} + +export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(generateRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } + } + } if (jsonData) { - let data = JSON.parse(jsonData); - if (data.rtd) { - addRealTimeData(data.rtd); + if (jsonData.rtd) { + addRealTimeData(jsonData.rtd); onDone(); - logInfo('DEBUG(getRealTimeData) - 1'); + logInfo('DEBUG(generateRealTimeData) - 1'); // Don't return - ensure the data is always fresh. } } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); +} +function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { let config = { api_hostname: rtdConfig.params.apiHostname, api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax - }; - let identity = { - type: rtdConfig.params.identityType + segtax: rtdConfig.params.segtax, + identity: {type: rtdConfig.params.identityType} }; - let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); - if (token !== null) { - let membership = dapUtils.dapGetMembership(config, token); - let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); - logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); - let data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); - onDone(); + let refreshMembership = true; + let token = dapUtils.dapGetTokenFromLocalStorage(); + logMessage('token is: ', token); + if (token !== null) { // If token is not null then check the membership in storage and add the RTD object + if (config.segtax == 504) { // Follow the encrypted membership path + dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server + refreshMembership = false; + } else { + dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server + refreshMembership = false; + } } + dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases } } @@ -117,6 +159,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } return true; } @@ -128,104 +173,190 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); - export const dapUtils = { + dapGetEntropy: function(resolve, reject) { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + reject(Error('window.dapCalculateEntropy function is not defined')) + } + }, - dapGetToken: function(config, identity, ttl) { + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_token'; let token = null; - - if (ttl == 0) { - localStorage.removeItem(storageName); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); + if (item) { + if (now < item.expires_at) { + token = item.token; + } } + return token; + }, - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null) { - item = { - expires_at: now - 1, - token: null - }; - } else { - token = item.token; - } - - if (now > item.expires_at) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, identity, - function(token, status, xhr) { - item.expires_at = now + ttl; - item.token = token; - localStorage.setItem(storageName, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - localStorage.setItem('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + dapRefreshToken: function(config, refreshMembership, onDone) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(token) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.token = token; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); + if (dapSSID) { + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); + } + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + if (refreshMembership) { + if (config.segtax == 504) { + dapUtils.dapRefreshEncryptedMembership(config, token, onDone); + } else { + dapUtils.dapRefreshMembership(config, token, onDone); } - }, - function(xhr, status, error) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - ); - } - - return token; + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() + } + ); }, - dapGetMembership: function(config, token) { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_membership'; - let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null || (now - item.expires_at) > maxTtl) { - item = { - expires_at: now - 1, - said: null, - cohorts: null, - attributes: null - }; - } else { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } } + return membership; + }, - // Always refresh the cached membership. + dapRefreshMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, - function(membership, status, xhr) { - item.expires_at = now + maxTtl; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } item.said = membership.said; item.cohorts = membership.cohorts; - localStorage.setItem(storageName, JSON.stringify(item)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); + + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(data, config.segtax); + onDone(); }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } } ); + }, - return membership; + dapGetEncryptedMembershipFromLocalStorage: function() { + let now = Math.round(Date.now() / 1000.0); // in seconds + let encMembership = null; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + encMembership = { + encryptedSegments: item.encryptedSegments + }; + } + } + return encMembership; + }, + + dapRefreshEncryptedMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {}; + let configAsync = {...config}; + dapUtils.dapEncryptedMembership(configAsync, token, onDone, + function(encToken, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(encToken) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.encryptedSegments = encToken; + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); + dapUtils.dapLog(item); + + let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); + dapUtils.checkAndAddRealtimeData(encData, config.segtax); + onDone(); + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } + } + ); + }, + + /** + * DESCRIPTION + * Extract expiry value from a token + */ + dapExtractExpiryFromToken: function(token) { + let exp = null; + if (token) { + const tokenArray = token.split('..'); + if (tokenArray && tokenArray.length > 0) { + let decode = atob(tokenArray[0]) + let header = JSON.parse(decode.replace(/"/g, '"')); + exp = header.exp; + } + } + return exp }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment. + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapMembershipToRtbSegment: function(membership, config) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': config.segtax + 'segtax': segtax }, segment: [] }; @@ -234,7 +365,83 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - return segment; + let data = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + return data; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + */ + dapGetEncryptedRtdObj: function(encToken, segtax) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': segtax + }, + segment: [] + }; + if (encToken != null) { + segment.segment.push({ id: encToken.encryptedSegments }); + } + let encData = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + } + } + } + }; + return encData; + }, + + checkAndAddRealtimeData: function(data, segtax) { + if (data.rtd) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { + logMessage('DEBUG(handleInit): rtb Object already added'); + } else { + addRealTimeData(data.rtd); + } + logInfo('DEBUG(checkAndAddRealtimeData) - 1'); + } + }, + + checkIfSegmentsAlreadyExist: function(rtd, segtax) { + let segmentsExist = false + let ortb2 = config.getConfig('ortb2') || {}; + if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { + for (let i = 0; i < ortb2.user.data.length; i++) { + let element = ortb2.user.data[i] + if (element.ext && element.ext.segtax == segtax) { + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); + break; + } + } + } + return segmentsExist }, dapLog: function(args) { @@ -248,6 +455,35 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, + isValidHttpsUrl: function(urlString) { + let url; + try { + url = new URL(urlString); + } catch (_) { + return false; + } + return url.protocol === 'https:'; + }, + + checkConsent: function(userConsent) { + let consent = true; + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('akamaiDapRtd submodule requires consent string to call API'); + consent = false; + } + } else if (userConsent && userConsent.usp) { + const usp = userConsent.usp; + consent = usp[1] !== 'N' && usp[2] !== 'Y'; + } + + return consent; + }, + /******************************************************************************* * * V2 (And Beyond) API @@ -293,23 +529,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } @@ -318,22 +554,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError'); + onError(null, 'Invalid identity object', 'ClientError', onDone); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } @@ -348,6 +584,11 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + if (entropyDict && entropyDict.entropy) { + apiParams.entropy = entropyDict.entropy; + } + let method; let body; let path; @@ -359,10 +600,16 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } + let customHeaders = {'Content-Type': 'application/json'}; + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); + if (dapSSID) { + customHeaders['Akamai-DAP-SS-ID'] = dapSSID; + } + let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -373,19 +620,16 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request); + onSuccess(token, request.status, request, onDone); }, error: (request, error) => { - onError(request, request.statusText, error); + onError(request, request.statusText, error, onDone); } }; ajax(url, cb, body, { method: method, - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-cache-on' - } + customHeaders: customHeaders }); }, @@ -411,7 +655,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_x1_tokenize + * // token from dap_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -422,13 +666,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onSuccess = null, onError = null) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } @@ -437,32 +681,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { - onError(request, request.status, error); + onError(request, request.status, error, onDone); } }; @@ -470,5 +714,91 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); + }, + + /** + * SYNOPSIS + * + * dapEncryptedMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token in encrypted format. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_tokenize + * + * dapEncryptedMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said after decryption + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error, onDone) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError', onDone); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + let encToken = request.getResponseHeader('Akamai-DAP-Token'); + onSuccess(encToken, request.status, request, onDone); + }, + error: (error, request) => { + onError(request, request.status, error, onDone); + } + }; + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-get-extracted-values' + } + }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index ade11b88602..5e3b93cc5fc 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,6 +17,7 @@ ``` pbjs.setConfig({ realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -25,9 +26,10 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - segtax: , - tokenTtl: 5, + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 // Maximum time for dapFP to run } } ] diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index fec0d1b6510..8fab37a433f 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -107,7 +107,7 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER + let traff = bid.params.traffic || BANNER; let placement = { placementId: bid.params.placement_id, groupId: bid.params.group_id, @@ -116,19 +116,7 @@ export const spec = { eids: [], floor: {} }; - if (typeof bid.getFloor === 'function') { - let tmpFloor = {}; - for (let size of placement.sizes) { - tmpFloor = bid.getFloor({ - currency: 'USD', - mediaType: traff, - size: size - }); - if (tmpFloor) { - placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; - } - } - } + if (bid.schain) { placement.schain = bid.schain; } @@ -147,9 +135,7 @@ export const spec = { } if (traff === BANNER) { placement.sizes = bid.mediaTypes[BANNER].sizes - } - - if (traff === VIDEO) { + } else if (traff === VIDEO) { placement.sizes = bid.mediaTypes[VIDEO].playerSize; placement.playerSize = bid.mediaTypes[VIDEO].playerSize; placement.minduration = bid.mediaTypes[VIDEO].minduration; @@ -167,6 +153,20 @@ export const spec = { placement.api = bid.mediaTypes[VIDEO].api; placement.linearity = bid.mediaTypes[VIDEO].linearity; } + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } + placements.push(placement); } return { diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 7ee8b1b7681..a6b631372e0 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -93,7 +93,7 @@ export const spec = { copyOptProperty(format[0].h, video, 'h'); } - copyOptProperty(bid.params.position, video, 'pos'); + copyOptProperty(bid.params.position || videoData.pos, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); @@ -105,7 +105,7 @@ export const spec = { const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; - copyOptProperty(bid.params.position, banner, 'pos'); + copyOptProperty(bid.params.position || bannerData.pos, banner, 'pos'); imp.banner = banner; } diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md index b422d0a536d..0239b4557e9 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -1,11 +1,11 @@ -## DAC User ID Submodule +## AudienceOne User ID Submodule -DAC ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. +AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. Please contact D.A.Consortium Inc. before using this ID. -## Building Prebid with DAC ID Support +## Building Prebid with AudienceOne ID Support -First, make sure to add the DAC ID submodule to your Prebid.js package with: +First, make sure to add the AudienceOne ID submodule to your Prebid.js package with: ``` gulp build --modules=dacIdSystem diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 21206109ee0..87b62e98097 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -17,7 +17,6 @@ const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const FTRACK_URL = 'https://d9.flashtalking.com/d9core'; const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); let consentInfo = { @@ -102,7 +101,7 @@ export const ftrackIdSubmodule = { } } - if (config.params && config.params.url && config.params.url === FTRACK_URL) { + if (config.params && config.params.url) { var ftrackScript = document.createElement('script'); ftrackScript.setAttribute('src', config.params.url); window.document.body.appendChild(ftrackScript); @@ -146,8 +145,8 @@ export const ftrackIdSubmodule = { utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".'); } - if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) { - utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".'); + if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url')) { + utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run.'); return false; } diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 0bf805fb94e..bc112989426 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -35,7 +36,10 @@ var pageView = { timezoneOffset: new Date().getTimezoneOffset(), language: window.navigator.language, vendor: window.navigator.vendor, - pageUrl: window.top.location.href, + pageUrl: (() => { + const ri = getRefererInfo(); + return ri.canonicalUrl || ri.referer; + })(), screenWidth: x, screenHeight: y }; diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 80aa038a9f7..5fc28c47ac5 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,4 +1,12 @@ -import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, isArray, parseQueryStringParameters, getWindowSelf } from '../src/utils.js'; +import { + deepAccess, + getWindowSelf, + isArray, + isStr, + parseQueryStringParameters, + replaceAuctionPrice, + triggerPixel +} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -6,19 +14,26 @@ import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'kobler'; const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { - return !!(bid && bid.bidId && bid.params && bid.params.placementId); + if (!bid || !bid.bidId) { + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', bid.sizes); + return isArray(sizes) && sizes.length > 0; }; export const buildRequests = function (validBidRequests, bidderRequest) { + const bidderEndpoint = isTest(validBidRequests[0]) ? DEV_BIDDER_ENDPOINT : BIDDER_ENDPOINT; return { method: 'POST', - url: BIDDER_ENDPOINT, + url: bidderEndpoint, data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), options: { contentType: 'application/json' @@ -27,14 +42,11 @@ export const buildRequests = function (validBidRequests, bidderRequest) { }; export const interpretResponse = function (serverResponse) { - const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const res = serverResponse.body; const bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - const adWithCorrectCurrency = b.adm - .replace(/\${AUCTION_PRICE_CURRENCY}/g, adServerPriceCurrency); bids.push({ requestId: b.impid, cpm: b.price, @@ -45,7 +57,7 @@ export const interpretResponse = function (serverResponse) { dealId: b.dealid, netRevenue: true, ttl: TIME_TO_LIVE_IN_SECONDS, - ad: adWithCorrectCurrency, + ad: b.adm, nurl: b.nurl, meta: { advertiserDomains: b.adomain @@ -58,13 +70,15 @@ export const interpretResponse = function (serverResponse) { }; export const onBidWon = function (bid) { - const cpm = bid.cpm || 0; - const cpmCurrency = bid.currency || SUPPORTED_CURRENCY; + // We intentionally use the price set by the publisher to replace the ${AUCTION_PRICE} macro + // instead of the `originalCpm` here. This notification is not used for billing, only for extra logging. + const publisherPrice = bid.cpm || 0; + const publisherCurrency = bid.currency || config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { - const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm) - .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) + const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice) + .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency) .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); triggerPixel(winNotificationUrl); @@ -73,17 +87,13 @@ export const onBidWon = function (bid) { export const onTimeout = function (timeoutDataArray) { if (isArray(timeoutDataArray)) { - const refererInfo = getRefererInfo(); - const pageUrl = (refererInfo && refererInfo.referer) - ? refererInfo.referer - : window.location.href; + const pageUrl = getPageUrlFromRefererInfo(); timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, - placement_id: deepAccess(timeoutData, 'params.0.placementId'), page_url: pageUrl, }); const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; @@ -92,13 +102,28 @@ export const onTimeout = function (timeoutDataArray) { } }; -function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { - const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; - const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) +function getPageUrlFromRefererInfo() { + const refererInfo = getRefererInfo(); + return (refererInfo && refererInfo.referer) + ? refererInfo.referer + : window.location.href; +} + +function getPageUrlFromRequest(validBidRequest, bidderRequest) { + // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL + if (isTest(validBidRequest) && config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + return (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href; +} +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + const imps = validBidRequests.map(buildOpenRtbImpObject); + const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, at: 1, @@ -106,13 +131,12 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { cur: [SUPPORTED_CURRENCY], imp: imps, device: { - devicetype: getDevice(), - geo: getGeo(validBidRequests[0]) + devicetype: getDevice() }, site: { page: pageUrl, }, - test: getTest(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]) }; return JSON.stringify(request); @@ -128,14 +152,8 @@ function buildOpenRtbImpObject(validBidRequest) { banner: { format: buildFormatArray(sizes), w: mainSize[0], - h: mainSize[1], - ext: { - kobler: { - pos: getPosition(validBidRequest) - } - } + h: mainSize[1] }, - tagid: validBidRequest.params.placementId, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, pmp: buildPmpObject(validBidRequest) @@ -157,17 +175,12 @@ function getDevice() { return 2; // personal computers } -function getGeo(validBidRequest) { - if (validBidRequest.params.zip) { - return { - zip: validBidRequest.params.zip - }; - } - return {}; +function getTestAsNumber(validBidRequest) { + return isTest(validBidRequest) ? 1 : 0; } -function getTest(validBidRequest) { - return validBidRequest.params.test ? 1 : 0; +function isTest(validBidRequest) { + return validBidRequest.params && validBidRequest.params.test === true; } function getSizes(validBidRequest) { @@ -188,10 +201,6 @@ function buildFormatArray(sizes) { }); } -function getPosition(validBidRequest) { - return parseInt(validBidRequest.params.position) || 0; -} - function getFloorInfo(validBidRequest, mainSize) { if (typeof validBidRequest.getFloor === 'function') { const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; @@ -209,11 +218,11 @@ function getFloorInfo(validBidRequest, mainSize) { } function getFloorPrice(validBidRequest) { - return parseFloat(validBidRequest.params.floorPrice) || 0.0; + return parseFloat(deepAccess(validBidRequest, 'params.floorPrice', 0.0)); } function buildPmpObject(validBidRequest) { - if (validBidRequest.params.dealIds) { + if (validBidRequest.params && validBidRequest.params.dealIds && isArray(validBidRequest.params.dealIds)) { return { deals: validBidRequest.params.dealIds.map(dealId => { return { diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md index 7a7b2388367..63b0c3ead68 100644 --- a/modules/koblerBidAdapter.md +++ b/modules/koblerBidAdapter.md @@ -12,14 +12,15 @@ This adapter currently only supports Banner Ads. # Parameters -| Parameter (in `params`) | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | -| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | -| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | -| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | -| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | -| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | +| Parameter (in `params`) | Scope | Type | Description | Example | +|-------------------------|----------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------| +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Enables providing a custom URL through config.pageUrl. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +## Implicit parameters + +Kobler identifies the placement using the combination of the page URL and the allowed sizes. As a result, it's important that the correct sizes are provided in `banner.sizes` in order for Kobler to correctly identify the placement. The main, desired format should be the first element of this array. # Test Parameters ```javascript @@ -31,17 +32,14 @@ This adapter currently only supports Banner Ads. } }, bids: [{ - bidder: 'kobler', - params: { - placementId: 'k5H7et3R0' - } + bidder: 'kobler' }] }]; ``` In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: -- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. -- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. +- Set the `test` parameter to `true`. +- Set `config.pageUrl` to `'https://www.tv2.no/mening-og-analyse/14555348/'`. This is necessary because Kobler only bids on recognized articles. Kobler runs its own test campaign to make sure there is always a bid for this specific page URL. # Test Optional Parameters ```javascript @@ -55,11 +53,8 @@ In order to see a sample bid from Kobler (without a proper setup), you have to a bids: [{ bidder: 'kobler', params: { - placementId: 'k5H7et3R0', - zip: '102 22', test: true, floorPrice: 5.0, - position: 1, dealIds: ['abc328745', 'mxw243253'] } }] diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 7bd86879e9d..d7f5f712252 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -36,10 +36,11 @@ function readnavIDFromCookie() { function readnvgnavFromLocalStorage() { var i; - const query = '^nvg|^nav'; + const query = /[nvga]{3}\d+/; for (i in window.localStorage) { if (i.match(query) || (!query && typeof i === 'string')) { - return storage.getDataFromLocalStorage(i.match(query).input); + const naveggId = storage.getDataFromLocalStorage(i.match(query).input).split('|')[0] + return naveggId.split('_')[0]; } } } diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index ae9cc4c818f..661a14ca0ef 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -18,6 +18,11 @@ export const novatiqIdSubmodule = { * @type {string} */ name: 'novatiq', + /** + * used to specify vendor id + * @type {number} + */ + gvlid: 1119, /** * decode the stored id value for passing to bid requests @@ -202,7 +207,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({moduleName: 'pubCommonId'}); + const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index db381555ef9..8312dacbdbb 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -151,9 +151,6 @@ function interpretResponse(serverResponse, bidRequest) { } bidResponses.push(bidResponse); } - - // eslint-disable-next-line no-console - console.log(JSON.stringify(bidResponses)); return bidResponses; } diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js new file mode 100644 index 00000000000..f93dd39a484 --- /dev/null +++ b/modules/taboolaBidAdapter.js @@ -0,0 +1,265 @@ +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getWindowSelf, getWindowTop} from '../src/utils.js' +import {getStorageManager} from '../src/storageManager.js'; + +const BIDDER_CODE = 'taboola'; +const GVLID = 42; +const CURRENCY = 'USD'; +export const END_POINT_URL = 'http://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +const USER_ID = 'user-id'; +const STORAGE_KEY = `taboola global:${USER_ID}`; +const COOKIE_KEY = 'trc_cookie_storage'; + +/** + * extract User Id by that order: + * 1. local storage + * 2. first party cookie + * 3. rendered trc + * 4. new user set it to 0 + */ +export const userData = { + storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), + getUserId: () => { + const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; + + try { + return getFromLocalStorage() || getFromCookie() || getFromTRC(); + } catch (ex) { + return 0; + } + }, + getFromCookie() { + const {cookiesAreEnabled, getCookie} = userData.storageManager; + if (cookiesAreEnabled()) { + const cookieData = getCookie(COOKIE_KEY); + const userId = userData.getCookieDataByKey(cookieData, USER_ID); + if (userId) { + return userId; + } + } + }, + getCookieDataByKey(cookieData, key) { + const [, value = ''] = cookieData.split(`${key}=`) + return value; + }, + getFromLocalStorage() { + const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager; + + if (hasLocalStorage() && localStorageIsEnabled()) { + return getDataFromLocalStorage(STORAGE_KEY); + } + }, + getFromTRC() { + return window.TRC ? window.TRC.user_id : 0; + } +} + +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} + +export const spec = { + supportedMediaTypes: [BANNER], + gvlid: GVLID, + code: BIDDER_CODE, + isBidRequestValid: (bidRequest) => { + return !!(bidRequest.sizes && + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const [bidRequest] = validBidRequests; + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; + const {publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, refererInfo); + const device = {ua: navigator.userAgent}; + const imps = getImps(validBidRequests); + const user = { + buyeruid: userData.getUserId(gdprConsent, uspConsent), + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1 + } + + const ortb2 = config.getConfig('ortb2') || { + badv: [], + bcat: [] + }; + + const request = { + id: bidderRequest.auctionId, + imp: imps, + site, + device, + source: {fd: 1}, + tmax: bidderRequest.timeout, + bcat: ortb2.bcat, + badv: ortb2.badv, + user, + regs + }; + + const url = [END_POINT_URL, publisherId].join('/'); + + return { + url, + method: 'POST', + data: JSON.stringify(request), + bids: validBidRequests, + options: { + withCredentials: false + }, + }; + }, + interpretResponse: (serverResponse, {bids}) => { + if (!bids) { + return []; + } + + const {bidResponses, cur: currency} = getBidResponses(serverResponse); + + if (!bidResponses) { + return []; + } + + return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); + }, +}; + +function getSiteProperties({publisherId, bcat = []}, refererInfo) { + const {getPageUrl, getReferrer} = internal; + return { + id: publisherId, + name: publisherId, + domain: window.location.host, + page: getPageUrl(refererInfo), + ref: getReferrer(refererInfo), + publisher: { + id: publisherId + }, + content: { + language: navigator.language + } + } +} + +function getImps(validBidRequests) { + return validBidRequests.map((bid, id) => { + const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + + return { + id: id + 1, + banner: getBanners(bid), + tagid: tagId, + bidfloor, + bidfloorcur, + }; + }); +} + +function getBanners(bid) { + return getSizes(bid.sizes); +} + +function getSizes(sizes) { + return { + format: sizes.map(size => { + return { + w: size[0], + h: size[1] + } + }) + } +} + +function getBidResponses({body}) { + if (!body) { + return []; + } + + const {seatbid, cur} = body; + + if (!seatbid.length || !seatbid[0].bid) { + return []; + } + + return { + bidResponses: seatbid[0].bid, + cur + }; +} + +function getBid(requestId, currency, bidResponse) { + if (!bidResponse) { + return; + } + + const { + price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; +} + +registerBidder(spec); diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md new file mode 100644 index 00000000000..a4213355049 --- /dev/null +++ b/modules/taboolaBidAdapter.md @@ -0,0 +1,49 @@ +# Overview + +``` +Module Name: Taboola Adapter +Module Type: Bidder Adapter +Maintainer: prebid@taboola.com +``` + +# Description + +Module that connects to Taboola bidder to fetch bids. +- Supports `display` format +- Uses `OpenRTB` standard + +The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com + +# Test Display Parameters +``` javascript + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'tester-placement', // Placement Name + publisherId: 'tester-pub', // your-publisher-id + bidfloor: 0.25, // Optional - default is null + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'] // Optional - default is [] + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | Type | +|----------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `tagId` | required | Tag ID / Placement Name
(as provided by Taboola) | `'Below The Article'` | `String` | +| `publisherId` | required | Alphabetic Publisher ID
(as provided by Taboola) | `'acme-publishing'` | `String` | +| `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | + + diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 87fdbe1396f..1fdd1eff179 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -99,7 +99,7 @@ export const spec = { user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { - tags: params.tags ? params.tags : {}, + tags: {...params.tags, shortname: params.shortname}, sid: params.sid ? params.sid : undefined } }; diff --git a/package-lock.json b/package-lock.json index b6daccf79ca..ca2e5412499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0-pre", + "version": "6.27.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 1dd7e1bc914..27c874c039a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0-pre", + "version": "6.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 80916e41324..ae891966ee1 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -3,6 +3,10 @@ import { ajax } from './ajax.js'; import { logMessage, _each } from './utils.js'; import * as events from './events.js' +export const _internal = { + ajax +}; + const { EVENTS: { AUCTION_INIT, @@ -61,7 +65,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } function _callEndpoint({ eventType, args, callback }) { - ajax(url, callback, JSON.stringify({ eventType, args })); + _internal.ajax(url, callback, JSON.stringify({ eventType, args })); } function _enqueue({ eventType, args }) { diff --git a/src/adloader.js b/src/adloader.js index db128c6d7ba..c73f83f300c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,7 +10,8 @@ const _approvedLoadExternalJSList = [ 'adagio', 'browsi', 'brandmetrics', - 'justtag' + 'justtag', + 'akamaidap' ] /** diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js new file mode 100644 index 00000000000..1023db882e8 --- /dev/null +++ b/test/mocks/analyticsStub.js @@ -0,0 +1,13 @@ +import {_internal} from '../../src/AnalyticsAdapter.js'; + +before(() => { + // stub out analytics networking to avoid random events polluting the global xhr mock + disableAjaxForAnalytics(); +}) + +export function disableAjaxForAnalytics() { + sinon.stub(_internal, 'ajax').callsFake(() => null); +} +export function enableAjaxForAnalytics() { + _internal.ajax.restore(); +} diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js index 9fb8fe87fa0..424100f870c 100644 --- a/test/mocks/xhr.js +++ b/test/mocks/xhr.js @@ -1,3 +1,4 @@ +import {getUniqueIdentifierStr} from '../../src/utils.js'; export let server = sinon.createFakeServer(); export let xhr = global.XMLHttpRequest; @@ -7,3 +8,24 @@ beforeEach(function() { server = sinon.createFakeServer(); xhr = global.XMLHttpRequest; }); + +const bid = getUniqueIdentifierStr().substring(4); +let fid = 0; + +/* eslint-disable */ +afterEach(function () { + if (this?.currentTest?.state === 'failed') { + const prepend = (() => { + const preamble = `[Failure ${bid}-${fid++}]`; + return (s) => s.split('\n').map(s => `${preamble} ${s}`).join('\n'); + })(); + + + console.log(prepend(`XHR mock state after failure (for test '${this.currentTest.fullTitle()}'): ${server.requests.length} requests`)) + server.requests.forEach((req, i) => { + console.log(prepend(`Request #${i}:`)); + console.log(prepend(JSON.stringify(req, null, 2))); + }) + } +}); +/* eslint-enable */ diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index d9199f47af9..f5dac22cb58 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; +import {disableAjaxForAnalytics, enableAjaxForAnalytics} from '../mocks/analyticsStub.js'; const REQUEST_BIDS = CONSTANTS.EVENTS.REQUEST_BIDS; const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; @@ -25,6 +26,9 @@ FEATURE: Analytics Adapters API AND an \`example\` instance of \`AnalyticsAdapter\`\n`, () => { let adapter; + before(enableAjaxForAnalytics); + after(disableAjaxForAnalytics); + beforeEach(function () { adapter = new AnalyticsAdapter(config); }); diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index b350c2bb529..25688abd99e 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -1,13 +1,14 @@ import {config} from 'src/config.js'; -import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; +import { + dapUtils, + generateRealTimeData, + akamaiDapRtdSubmodule, + storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP, +} from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; -import logMessage from 'src/utils.js' const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { - let getDataFromLocalStorageStub; - let getDapTokenStub; - const testReqBidsConfigObj = { adUnits: [ { @@ -18,7 +19,21 @@ describe('akamaiDapRtdProvider', function() { const onDone = function() { return true }; - const onSuccess = function() { return ('request', 200, 'success') }; + const sampleGdprConsentConfig = { + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } + }; + + const sampleUspConsentConfig = { + 'usp': '1YYY' + }; + + const sampleIdentity = { + type: 'dap-signature:1.0.0' + }; const cmoduleConfig = { 'name': 'dap', @@ -28,8 +43,19 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503, - 'tokenTtl': 5 + 'segtax': 503 + } + } + + const emoduleConfig = { + 'name': 'dap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 504 } } @@ -37,81 +63,119 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503 + 'segtax': 503, + 'identity': sampleIdentity } - const sampleIdentity = { - type: 'dap-signature:1.0.0' + + const esampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 504, + 'identity': sampleIdentity + } + let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; + const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; + const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; + const rtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const encRtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + segtax: 504, + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj] + } + } + } + }; + + let membership = { + said: cachedMembership.said, + cohorts: cachedMembership.cohorts, + attributes: null + }; + let encMembership = { + encryptedSegments: cachedEncryptedMembership.encryptedSegments + }; + encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); + const cachedEncRtd = { + rtd: { + ortb2: { + user: { + data: [encRtdUserObj] + } + } + } }; beforeEach(function() { config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + storage.removeDataFromLocalStorage(DAP_TOKEN); + storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_SS_ID); }); afterEach(function () { - getDataFromLocalStorageStub.restore(); }); describe('akamaiDapRtdSubmodule', function() { it('successfully instantiates', function () { - expect(akamaiDapRtdSubmodule.init()).to.equal(true); + expect(akamaiDapRtdSubmodule.init()).to.equal(true); }); }); describe('Get Real-Time Data', function() { it('gets rtd from local storage cache', function() { - const rtdConfig = { - params: { - segmentCache: true - } - }; - const bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) + let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) + let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) + let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('should initalise and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + dapGetRtdObjStub.restore() + dapGetMembershipFromLocalStorageStub.restore() + dapGetEncryptedRtdObjStub.restore() + dapGetEncryptedMembershipFromLocalStorageStub.restore() }); }); describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -121,10 +185,10 @@ describe('akamaiDapRtdProvider', function() { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -135,40 +199,54 @@ describe('akamaiDapRtdProvider', function() { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); const config = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', 'segtax': 503 }; + const encConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 504 + }; let identity = { type: 'dap-signature:1.0.0' }; - expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); }); + }); - it('dapGetToken success', function () { - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); - expect(dapUtils.dapGetToken(sampleConfig, 'token', - function(token, status, xhr) { - }, - function(xhr, status, error) { - } - )).to.be.equal(null); - dapTokenizeStub.restore(); + describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { + it('dapGetTokenFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); + }); + + it('dapGetMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); + expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); + }); + + it('dapGetEncryptedMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -178,10 +256,38 @@ describe('akamaiDapRtdProvider', function() { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapEncMembership', function () { + it('dapEncMembership success callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapEncMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -192,55 +298,243 @@ describe('akamaiDapRtdProvider', function() { describe('dapMembership', function () { it('should invoke the getDapToken and getDapMembership', function () { - let config = { - api_hostname: cmoduleConfig.params.apiHostname, - api_version: cmoduleConfig.params.apiVersion, - domain: cmoduleConfig.params.domain, - segtax: cmoduleConfig.params.segtax - }; - let identity = { - type: cmoduleConfig.params.identityType - }; - let membership = { said: 'item.said1', cohorts: 'item.cohorts', attributes: null }; - let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); - let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); - getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); - expect(getDapTokenStub.calledOnce).to.be.equal(true); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); expect(getDapMembershipStub.calledOnce).to.be.equal(true); - getDapTokenStub.restore(); getDapMembershipStub.restore(); - dapTokenizeStub.restore(); }); }); - describe('dapMembershipToRtbSegment', function () { - it('dapMembershipToRtbSegment', function () { - let membership1 = { - said: 'item.said1', - cohorts: 'item.cohorts', - attributes: null + describe('dapEncMembership test', function () { + it('should invoke the getDapToken and getEncDapMembership', function () { + let encMembership = { + encryptedSegments: 'enc.seg', }; + + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); + expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); + getDapEncMembershipStub.restore(); + }); + }); + + describe('dapGetRtdObj test', function () { + it('dapGetRtdObj', function () { const config = { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - tokenTtl: 5, segtax: 503 }; - let identity = { - type: 'dap-signature:1.0.0' - }; - - expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} - expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); + expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); + }); + }); + + describe('checkAndAddRealtimeData test', function () { + it('add realtime data for segtax 503 and 504', function () { + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(cachedRtd, 503); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + }); + }); + + describe('dapExtractExpiryFromToken test', function () { + it('test dapExtractExpiryFromToken function', function () { + let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); + }); + }); + + describe('dapRefreshToken test', function () { + it('test dapRefreshToken success response', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with deviceid 100', function () { + dapUtils.dapRefreshToken(esampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with exp claim', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); + }); + + it('test dapRefreshToken error response', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache + }); + }); + + describe('dapRefreshMembership test', function () { + it('test dapRefreshMembership success response', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + }); + + it('test dapRefreshMembership success response with exp claim', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); + }); + + it('test dapRefreshMembership 400 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshMembership 403 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapRefreshEncryptedMembership test', function () { + it('test dapRefreshEncryptedMembership success response', function () { + let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); + }); + + it('test dapRefreshEncryptedMembership success response with exp claim', function () { + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); + }); + + it('test dapRefreshEncryptedMembership error response', function () { + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshEncryptedMembership 403 error response', function () { + generateRealTimeData({}, () => {}, emoduleConfig, {}); + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapGetEncryptedMembershipFromLocalStorage test', function () { + it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); + }); + + it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { + let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds + let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) + expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); + }); + }); + + describe('Akamai-DAP-SS-ID test', function () { + it('Akamai-DAP-SS-ID present in response header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let sampleSSID = 'Test_SSID_Spec'; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + responseHeader['Akamai-DAP-SS-ID'] = sampleSSID; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); + }); + + it('Test if Akamai-DAP-SS-ID is present in request header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(ssidHeader).to.be.equal('Test_SSID_Spec'); + }); + }); + + describe('Test gdpr and usp consent handling', function () { + it('Gdpr applies and gdpr consent string not present', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); + }); + + it('Gdpr applies and gdpr consent string is present', function () { + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); + }); + + it('USP consent present and user have opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); + }); + + it('USP consent present and user have not been provided with option to opt out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); + }); + + it('USP consent present and user have not opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 53169326d3b..ebc9879bb84 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -72,6 +72,7 @@ describe('Conversant adapter tests', function() { video: { context: 'instream', playerSize: [632, 499], + pos: 3 } }, placementCode: 'pcode003', @@ -108,12 +109,14 @@ describe('Conversant adapter tests', function() { { bidder: 'conversant', params: { - site_id: siteId + site_id: siteId, + position: 2, }, mediaTypes: { video: { context: 'instream', - mimes: ['video/mp4', 'video/x-flv'] + mimes: ['video/mp4', 'video/x-flv'], + pos: 7, } }, placementCode: 'pcode005', @@ -147,6 +150,23 @@ describe('Conversant adapter tests', function() { bidId: 'bid006', bidderRequestId: '117d765b87bed38', auctionId: 'req000' + }, + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [468, 60]], + pos: 5 + } + }, + placementCode: 'pcode001', + transactionId: 'tx001', + bidId: 'bid007', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' } ]; @@ -245,7 +265,7 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(7); + expect(payload.imp).to.be.an('array').with.lengthOf(8); expect(payload.imp[0]).to.have.property('id', 'bid000'); expect(payload.imp[0]).to.have.property('secure', 1); @@ -287,7 +307,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[3]).to.not.have.property('tagid'); expect(payload.imp[3]).to.have.property('video'); - expect(payload.imp[3].video).to.not.have.property('pos'); + expect(payload.imp[3].video).to.have.property('pos', 3); expect(payload.imp[3].video).to.have.property('w', 632); expect(payload.imp[3].video).to.have.property('h', 499); expect(payload.imp[3].video).to.have.property('mimes'); @@ -325,7 +345,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[5]).to.not.have.property('tagid'); expect(payload.imp[5]).to.have.property('video'); - expect(payload.imp[5].video).to.not.have.property('pos'); + expect(payload.imp[5].video).to.have.property('pos', 2); expect(payload.imp[5].video).to.not.have.property('w'); expect(payload.imp[5].video).to.not.have.property('h'); expect(payload.imp[5].video).to.have.property('mimes'); @@ -345,6 +365,17 @@ describe('Conversant adapter tests', function() { expect(payload.imp[6].ext).to.have.property('data'); expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + expect(payload.imp[7]).to.have.property('id', 'bid007'); + expect(payload.imp[7]).to.have.property('secure', 1); + expect(payload.imp[7]).to.have.property('bidfloor', 0); + expect(payload.imp[7]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[7]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[7]).to.not.have.property('tagid'); + expect(payload.imp[7]).to.have.property('banner'); + expect(payload.imp[7].banner).to.have.property('pos', 5); + expect(payload.imp[7].banner).to.have.property('format'); + expect(payload.imp[7].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index ff7b0aad48c..8e203510b10 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -305,6 +305,15 @@ let bid_request = { } describe('DatablocksAdapter', function() { + before(() => { + // stub out queue metric to avoid it polluting the global xhr mock during other tests + sinon.stub(spec, 'queue_metric').callsFake(() => null); + }); + + after(() => { + spec.queue_metric.restore(); + }); + describe('All needed functions are available', function() { it(`isBidRequestValid is present and type function`, function () { expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') @@ -377,14 +386,6 @@ describe('DatablocksAdapter', function() { }); }) - describe('queue / send metrics', function() { - it('Should return true', function() { - expect(spec.queue_metric({type: 'test'})).to.be.true; - expect(spec.queue_metric('string')).to.be.false; - expect(spec.send_metrics()).to.be.true; - }); - }) - describe('get_viewability', function() { it('Should return undefined', function() { expect(spec.get_viewability()).to.equal(undefined); diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 407ceb305a2..76f77505105 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -51,7 +51,7 @@ describe('finteza analytics adapter', function () { describe('track', () => { describe('bid request', () => { - it('builds and sends data', function () { + it('builds and sends request data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -95,7 +95,7 @@ describe('finteza analytics adapter', function () { }); describe('bid response', () => { - it('builds and sends data', function () { + it('builds and sends response data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -154,7 +154,7 @@ describe('finteza analytics adapter', function () { }); describe('bid won', () => { - it('builds and sends data', function () { + it('builds and sends bid won data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -194,7 +194,7 @@ describe('finteza analytics adapter', function () { }); describe('bid timeout', () => { - it('builds and sends data', function () { + it('builds and sends timeout data', function () { const bidderCode = 'biDDer789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index b0843ebb99e..a1c49f321a3 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -95,15 +95,7 @@ describe('FTRACK ID System', () => { delete configMock1.params.url; ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); - }); - - it(`should be rejected if 'storage.param.url' does not exist or is not 'https://d9.flashtalking.com/d9core'`, () => { - let configMock1 = JSON.parse(JSON.stringify(configMock)); - configMock1.params.url = 'https://d9.NOT.flashtalking.com/d9core'; - - ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run.`); }); }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 76c2c287989..8b45fead744 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -16,7 +16,7 @@ function createBidderRequest(auctionId, timeout, pageUrl) { } function createValidBidRequest(params, bidId, sizes) { - return { + const validBidRequest = { adUnitCode: 'adunit-code', bidId: bidId || '22c4871113f461', bidder: 'kobler', @@ -28,11 +28,12 @@ function createValidBidRequest(params, bidId, sizes) { sizes: sizes || [[300, 250], [320, 100]] } }, - params: params || { - placementId: 'tpw58278' - }, transactionTd: '04314114-15bd-4638-8664-bdb8bdc60bff' }; + if (params) { + validBidRequest.params = params; + } + return validBidRequest; } describe('KoblerAdapter', function () { @@ -56,7 +57,7 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without params as valid', function () { + it('should not accept a request without mediaTypes and sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589' }; @@ -66,11 +67,56 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without placementId as valid', function () { + it('should not accept a request without mediaTypes and string sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc' + sizes: 'string' + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [], + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: 'string' } }; @@ -79,12 +125,67 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should accept a request with bidId and placementId as valid', function () { + it('should not accept a request without sizes and mediaTypes.banner.sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc', - placementId: '8bde0923-1409-4253-9594-495b58d931ba' + mediaTypes: { + banner: {} + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: 'string' + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and empty mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [] + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should accept a request with bidId and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [[5, 5]] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with bidId and mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [[0, 0]] + } } }; @@ -114,13 +215,10 @@ describe('KoblerAdapter', function () { const firstSize = [400, 800]; const secondSize = [450, 950]; const sizes = [firstSize, secondSize]; - const placementId = 'tsjs86325'; const bidId = '3a56a019-4835-4f75-811c-76fac6853a2c'; const validBidRequests = [ createValidBidRequest( - { - placementId: placementId - }, + undefined, bidId, sizes ) @@ -132,7 +230,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp.length).to.be.equal(1); expect(openRtbRequest.imp[0].id).to.be.equal(bidId); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); expect(openRtbRequest.imp[0].banner.w).to.be.equal(firstSize[0]); expect(openRtbRequest.imp[0].banner.h).to.be.equal(firstSize[1]); expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(2); @@ -163,40 +260,31 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(0); }); - it('should use 0 as default position', function () { - const validBidRequests = [createValidBidRequest()]; - const bidderRequest = createBidderRequest(); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(0); - }); - - it('should read zip from valid bid requests', function () { - const zip = '700 02'; + it('should read test from valid bid requests', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'nmah8324234', - zip: zip + test: true } ) ]; const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.device.geo.zip).to.be.equal(zip); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('example.com'); + expect(openRtbRequest.test).to.be.equal(1); }); - it('should read test from valid bid requests', function () { + it('should read pageUrl from config when testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ createValidBidRequest( { - placementId: 'zwop842799', test: true } ) @@ -204,49 +292,40 @@ describe('KoblerAdapter', function () { const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://testing-url.com'); expect(openRtbRequest.test).to.be.equal(1); }); - it('should read floorPrice from valid bid requests', function () { - const floorPrice = 4.343; + it('should not read pageUrl from config when not testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ - createValidBidRequest( - { - placementId: 'oqr3224234', - floorPrice: floorPrice - } - ) + createValidBidRequest() ]; - const bidderRequest = createBidderRequest(); + const bidderRequest = createBidderRequest( + 'f85d61cc-ed11-4b6c-aefb-87943263cedb', + 2000, + 'https://non-testing-url.net' + ); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://non-testing-url.net'); + expect(openRtbRequest.test).to.be.equal(0); }); - it('should read position from valid bid requests', function () { - const placementId = 'yzksf234592'; + it('should read floorPrice from valid bid requests', function () { + const floorPrice = 4.343; const validBidRequests = [ createValidBidRequest( { - placementId: placementId, - position: 1 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 2 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 3 + floorPrice: floorPrice } ) ]; @@ -255,13 +334,8 @@ describe('KoblerAdapter', function () { const result = spec.buildRequests(validBidRequests, bidderRequest); const openRtbRequest = JSON.parse(result.data); - expect(openRtbRequest.imp.length).to.be.equal(3); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(1); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[1].banner.ext.kobler.pos).to.be.equal(2); - expect(openRtbRequest.imp[1].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[2].banner.ext.kobler.pos).to.be.equal(3); - expect(openRtbRequest.imp[2].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); }); it('should read dealIds from valid bid requests', function () { @@ -270,13 +344,11 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'rsl1239823', dealIds: dealIds1 } ), createValidBidRequest( { - placementId: 'pqw234232', dealIds: dealIds2 } ) @@ -353,10 +425,7 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'pcha322364', - zip: '0015', floorPrice: 5.6234, - position: 1, dealIds: ['623472534328234'] }, '953ee65d-d18a-484f-a840-d3056185a060', @@ -364,19 +433,14 @@ describe('KoblerAdapter', function () { ), createValidBidRequest( { - placementId: 'sdfgoi32y4', floorPrice: 3.2543, - position: 2, dealIds: ['92368234753283', '263845832942'] }, '8320bf79-9d90-4a17-87c6-5d505706a921', [[400, 500], [200, 250], [300, 350]] ), createValidBidRequest( - { - placementId: 'gwms2738647', - position: 3 - }, + undefined, 'd0de713b-32e3-4191-a2df-a007f08ffe72', [[800, 900]] ) @@ -406,14 +470,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 600, - ext: { - kobler: { - pos: 1 - } - } + h: 600 }, - tagid: 'pcha322364', bidfloor: 5.6234, bidfloorcur: 'USD', pmp: { @@ -442,14 +500,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 500, - ext: { - kobler: { - pos: 2 - } - } + h: 500 }, - tagid: 'sdfgoi32y4', bidfloor: 3.2543, bidfloorcur: 'USD', pmp: { @@ -473,24 +525,15 @@ describe('KoblerAdapter', function () { } ], w: 800, - h: 900, - ext: { - kobler: { - pos: 3 - } - } + h: 900 }, - tagid: 'gwms2738647', bidfloor: 0, bidfloorcur: 'USD', pmp: {} } ], device: { - devicetype: 2, - geo: { - zip: '0015' - } + devicetype: 2 }, site: { page: 'bid.kobler.no' @@ -531,7 +574,7 @@ describe('KoblerAdapter', function () { dealid: '', w: 320, h: 250, - adm: '', + adm: '', adomain: [ 'https://kobler.no' ] @@ -544,7 +587,7 @@ describe('KoblerAdapter', function () { dealid: '2783483223432342', w: 580, h: 400, - adm: '', + adm: '', adomain: [ 'https://bid.kobler.no' ] @@ -568,7 +611,7 @@ describe('KoblerAdapter', function () { dealId: '', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -586,7 +629,7 @@ describe('KoblerAdapter', function () { dealId: '2783483223432342', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -628,6 +671,7 @@ describe('KoblerAdapter', function () { } }); spec.onBidWon({ + originalCpm: 1.532, cpm: 8.341, currency: 'NOK', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', @@ -670,22 +714,14 @@ describe('KoblerAdapter', function () { auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', timeout: 100, - params: [ - { - placementId: 'xrwg62731', - } - ], + params: [], }, { adUnitCode: 'adunit-code-2', auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', timeout: 100, - params: [ - { - placementId: 'bc482234', - } - ], + params: [], } ]); @@ -693,12 +729,12 @@ describe('KoblerAdapter', function () { expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + - 'placement_id=xrwg62731&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + - 'placement_id=bc482234&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); }); }); diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js deleted file mode 100644 index 7d73ecd5d44..00000000000 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ /dev/null @@ -1,146 +0,0 @@ -import * as medianetRTD from '../../../modules/medianetRtdProvider.js'; -import * as sinon from 'sinon'; -import { assert } from 'chai'; - -let sandbox; -let setDataSpy; -let getTargetingDataSpy; -let onPrebidRequestBidSpy; - -const conf = { - dataProviders: [{ - 'name': 'medianet', - 'params': { - 'cid': 'customer_id', - } - }] -}; - -describe('medianet realtime module', function () { - beforeEach(function () { - sandbox = sinon.sandbox.create(); - window.mnjs = window.mnjs || {}; - window.mnjs.que = window.mnjs.que || []; - window.mnjs.setData = setDataSpy = sandbox.spy(); - window.mnjs.getTargetingData = getTargetingDataSpy = sandbox.spy(); - window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = sandbox.spy(); - }); - - afterEach(function () { - sandbox.restore(); - window.mnjs = {}; - }); - - it('init should return false when customer id is passed', function () { - assert.equal(medianetRTD.medianetRtdModule.init({}), false); - }); - - it('init should return true when customer id is passed', function () { - assert.equal(medianetRTD.medianetRtdModule.init(conf.dataProviders[0]), true); - }); - - it('init should pass config to js when loaded', function () { - medianetRTD.medianetRtdModule.init(conf.dataProviders[0]); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(setDataSpy.called, true); - assert.equal(setDataSpy.args[0][0].name, 'initIRefresh'); - }); - - it('auctionInit should pass information to js when loaded', function () { - const auctionObject = {adUnits: []}; - medianetRTD.medianetRtdModule.onAuctionInitEvent(auctionObject); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(setDataSpy.called, true); - assert.equal(setDataSpy.args[0][0].name, 'auctionInit'); - assert.deepEqual(setDataSpy.args[0][0].data, {auction: auctionObject}); - }); - - describe('getTargeting should work correctly', function () { - it('should return empty if not loaded', function () { - window.mnjs.loaded = false; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); - }); - - it('should return ad unit codes when ad units are present', function () { - const adUnitCodes = ['code1', 'code2']; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { - code1: {'mnadc': 'code1'}, - code2: {'mnadc': 'code2'}, - }); - }); - - it('should call mnjs.getTargetingData if loaded', function () { - window.mnjs.loaded = true; - medianetRTD.medianetRtdModule.getTargetingData([]); - assert.equal(getTargetingDataSpy.called, true); - }); - }); - - describe('getBidRequestData should work correctly', function () { - it('callback should be called when we are not interested in request', function () { - const requestBidsProps = { - adUnits: [{ - code: 'code1', bids: [], - }], - adUnitCodes: ['code1'], - }; - const callbackSpy = sandbox.spy(); - medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequest should always be called'); - assert.equal(callbackSpy.called, true, 'when onPrebidRequest returns nothing callback should be called immediately'); - }); - - it('we should wait for callback till onComplete', function () { - const requestBidsProps = { - adUnits: [{ - code: 'code1', bids: [], - }], - adUnitCodes: ['code1'], - }; - - const refreshInformation = { - mnrf: '1', - mnrfc: 2, - }; - - const callbackSpy = sandbox.spy(); - const onCompleteSpy = sandbox.spy(); - window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = () => { - onPrebidRequestBidSpy.called = true; - return {onComplete: onCompleteSpy}; - }; - medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(callbackSpy.called, false, 'callback should not be called, as we are returning a request from onPrebidRequestBid'); - assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequestBid should be called once'); - assert.equal(onCompleteSpy.called, true, 'onComplete should be passed callback'); - assert.isFunction(onCompleteSpy.args[0][0], 'onCompleteSpy first argument error callback should be a function'); - assert.isFunction(onCompleteSpy.args[0][1], 'onCompleteSpy second argument success callback should be a function'); - onCompleteSpy.args[0][0](); - assert.equal(callbackSpy.callCount, 1, 'callback should be called when error callback is triggered'); - onCompleteSpy.args[0][1]({}, { - 'code1': {ext: {refresh: refreshInformation}} - }); - assert.equal(callbackSpy.callCount, 2, 'callback should be called when success callback is triggered'); - assert.isObject(requestBidsProps.adUnits[0].ortb2Imp, 'ORTB object should be set'); - assert.deepEqual(requestBidsProps.adUnits[0].ortb2Imp.ext.refresh, refreshInformation, 'ORTB should have refresh information should be set'); - }); - }); -}); diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js index c0973a05372..2c4f1cda859 100644 --- a/test/spec/modules/naveggIdSystem_spec.js +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -1,6 +1,15 @@ import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; describe('naveggId', function () { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + afterEach(() => { + sandbox.restore(); + }); + it('should NOT find navegg id', function () { let id = naveggIdSubmodule.getId(); @@ -14,8 +23,23 @@ describe('naveggId', function () { }) it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { - sinon.stub(storage, 'getDataFromLocalStorage').withArgs('nvggid').returns('test-ninvggidd'); + storage.getDataFromLocalStorage.callsFake(() => 'test-ninvggidd') + let id = naveggIdSubmodule.getId(); expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) }) + + it('getId() should return "test-nvggid" id from local storage NAV0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nav0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nav0'}) + }) + + it('getId() should return "test-nvggid" id from local storage NVG0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nvg0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nvg0'}) + }) }); diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 084c533b5b5..5b9f35c5bcf 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -124,8 +124,6 @@ describe('ProxistoreBidAdapter', function () { bid.params['bidFloor'] = 1; let req = spec.buildRequests([bid], bidderRequest); data = JSON.parse(req.data); - // eslint-disable-next-line no-console - console.log(data.bids[0]); expect(data.bids[0].floor).equal(1); bid.getFloor = function () { return { currency: 'USD', floor: 1.0 }; diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 83a13f0db7b..c515f302c32 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,92 +1,91 @@ -import {expect} from 'chai'; -import {spec} from 'modules/sovrnBidAdapter.js'; -import {config} from 'src/config.js'; +import {expect} from 'chai' +import {spec} from 'modules/sovrnBidAdapter.js' +import {config} from 'src/config.js' import * as utils from 'src/utils.js' -const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; +const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$` const baseBidRequest = { - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370 + bidder: 'sovrn', + params: { + tagid: 403370 }, - 'adUnitCode': 'adunit-code', - 'sizes': [ + adUnitCode: 'adunit-code', + sizes: [ [300, 250], [300, 600] ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', } const baseBidderRequest = { refererInfo: { referer: 'http://example.com/page.html', } -}; +} describe('sovrnBidAdapter', function() { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true); - }); + expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true) + }) it('should return false when tagid not passed correctly', function () { const bidRequest = { ...baseBidRequest, - 'params': { + params: { ...baseBidRequest.params, - 'tagid': 'ABCD' + tagid: 'ABCD' }, - }; + } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'params': {} + params: {} } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require video params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { 'video': { } } } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) + }) describe('buildRequests', function () { describe('basic bid parameters', function() { - const request = spec.buildRequests([baseBidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([baseBidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); + expect(request.method).to.equal('POST') + }) it('attaches source and version to endpoint URL as query params', function () { expect(request.url).to.equal(ENDPOINT) - }); + }) it('sets the proper banner object', function() { const bannerBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { banner: {} } } const request = spec.buildRequests([bannerBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -105,7 +104,7 @@ describe('sovrnBidAdapter', function() { const startdelay = 0 const videoBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { video: { mimes, protocols, @@ -117,7 +116,6 @@ describe('sovrnBidAdapter', function() { } } const request = spec.buildRequests([videoBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -131,9 +129,9 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); - }); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') + }) it('includes the ad unit code in the request', function() { const impression = payload.imp[0] @@ -142,22 +140,21 @@ describe('sovrnBidAdapter', function() { it('converts tagid to string', function () { expect(request.data).to.contain('"tagid":"403370"') - }); + }) }) it('accepts a single array as a size', function() { const singleSizeBidRequest = { ...baseBidRequest, - 'params': { - 'iv': 'vet' + params: { + iv: 'vet' }, - 'sizes': [300, 250], - 'mediaTypes': { + sizes: [300, 250], + mediaTypes: { banner: {} }, } const request = spec.buildRequests([singleSizeBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -176,22 +173,21 @@ describe('sovrnBidAdapter', function() { const request = spec.buildRequests([ivBidRequest], baseBidderRequest) expect(request.url).to.contain('iv=vet') - }); + }) it('sends gdpr info if exists', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - 'consentString': 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', - 'gdprApplies': true + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true }, - 'bids': [baseBidRequest] - }; - + bids: [baseBidRequest] + } const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(regs.ext.gdpr).to.exist.and.to.be.a('number') @@ -203,14 +199,13 @@ describe('sovrnBidAdapter', function() { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': '1NYN', - 'bids': [baseBidRequest] + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + uspConsent: '1NYN', + bids: [baseBidRequest] } - const data = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) @@ -219,21 +214,20 @@ describe('sovrnBidAdapter', function() { it('should add schain if present', function() { const schainRequest = { ...baseBidRequest, - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'directseller.com', - 'sid': '00001', - 'rid': 'BidRequest1', - 'hp': 1 + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 } ] } } const schainRequests = [schainRequest, baseBidRequest] - const data = JSON.parse(spec.buildRequests(schainRequests, baseBidderRequest).data) expect(data.source.ext.schain.nodes.length).to.equal(1) @@ -248,7 +242,6 @@ describe('sovrnBidAdapter', function() { } } const criteoIdRequests = [criteoIdRequest, baseBidRequest] - const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext const firstEID = ext.eids[0] const secondEID = ext.eids[1] @@ -294,7 +287,6 @@ describe('sovrnBidAdapter', function() { bidfloor: 2.00 } } - const request = spec.buildRequests([floorBid], baseBidderRequest) const payload = JSON.parse(request.data) @@ -309,7 +301,6 @@ describe('sovrnBidAdapter', function() { tagid: 1234, bidfloor: 2.00 } - const request = spec.buildRequests([floorBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -359,7 +350,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBidRequest], baseBidderRequest) const payload = JSON.parse(request.data) @@ -381,7 +371,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -390,102 +379,109 @@ describe('sovrnBidAdapter', function() { expect(impression.ext.deals).to.deep.equal(['seg1', 'seg2']) }) }) - }); + }) describe('interpretResponse', function () { - let response; + let response const baseResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'ad': decodeURIComponent(``), + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 90, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(``), } const videoBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': 'key%3Dvalue', - 'h': 480, - 'w': 640 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: 'key%3Dvalue', + h: 480, + w: 640 } const bannerBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': '', - 'h': 90, - 'w': 728 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: '', + h: 90, + w: 728 } + beforeEach(function () { response = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ + id: '37386aade21a71', + seatbid: [{ + bid: [{ ...bannerBid }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { - ...baseResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(response); + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60000, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(`>`) + } + const result = spec.interpretResponse(response) - expect(result[0]).to.have.deep.keys(expectedResponse) - }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) + }) it('crid should default to the bid id if not on the response', function () { - delete response.body.seatbid[0].bid[0].crid; + delete response.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseResponse, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'ad': decodeURIComponent(``), + creativeId: response.body.seatbid[0].bid[0].id, + ad: decodeURIComponent(``), } + const result = spec.interpretResponse(response) - const result = spec.interpretResponse(response); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - response.body.seatbid[0].bid[0].dealid = 'baking'; + response.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(response) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ext = { 'ttl': 480 } + response.body.seatbid[0].bid[0].ext = { ttl: 480 } const expectedResponse = { ...baseResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(response) expect(result[0]).to.deep.equal(expectedResponse) @@ -494,20 +490,19 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); + expect(result.length).to.equal(0) + }) it('should get the correct bid response with 2 different bids', function () { const expectedVideoResponse = { ...baseResponse, - 'vastXml': decodeURIComponent(videoBid.adm) + vastXml: decodeURIComponent(videoBid.adm) } delete expectedVideoResponse.ad @@ -533,90 +528,87 @@ describe('sovrnBidAdapter', function() { expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedResponse)) }) - }); + }) describe('interpretResponse video', function () { - let videoResponse; - const bidAdm = 'key%3Dvalue'; - const decodedBidAdm = decodeURIComponent(bidAdm); + let videoResponse + const bidAdm = 'key%3Dvalue' + const decodedBidAdm = decodeURIComponent(bidAdm) const baseVideoResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 640, - 'height': 480, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'video', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'vastXml': decodedBidAdm + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 640, + height: 480, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ttl: 90, + meta: { advertiserDomains: [] }, + vastXml: decodedBidAdm } + beforeEach(function () { videoResponse = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': bidAdm, - 'h': 480, - 'w': 640 + id: '37386aade21a71', + seatbid: [{ + bid: [{ + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: bidAdm, + h: 480, + w: 640 }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { ...baseVideoResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(videoResponse); + ttl: 60000, + } + const result = spec.interpretResponse(videoResponse) expect(result[0]).to.have.deep.keys(expectedResponse) - }); + }) it('crid should default to the bid id if not on the response', function () { - delete videoResponse.body.seatbid[0].bid[0].crid; + delete videoResponse.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseVideoResponse, - 'creativeId': videoResponse.body.seatbid[0].bid[0].id, + creativeId: videoResponse.body.seatbid[0].bid[0].id, } + const result = spec.interpretResponse(videoResponse) - const result = spec.interpretResponse(videoResponse); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - videoResponse.body.seatbid[0].bid[0].dealid = 'baking'; + videoResponse.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseVideoResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(videoResponse) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { videoResponse.body.seatbid[0].bid[0].ext = { 'ttl': 480 } const expectedResponse = { ...baseVideoResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(videoResponse) expect(result[0]).to.deep.equal(expectedResponse) @@ -625,52 +617,51 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); - }); + expect(result.length).to.equal(0) + }) + }) describe('getUserSyncs ', function() { - const syncOptions = { iframeEnabled: true, pixelEnabled: false }; - const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; + const syncOptions = { iframeEnabled: true, pixelEnabled: false } + const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false } const serverResponse = [ { - 'body': { - 'id': '546956d68c757f', - 'seatbid': [ + body: { + id: '546956d68c757f', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': 'a_448326_16c2ada014224bee815a90d2248322f5', - 'impid': '2a3826aae345f4', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 160, - 'h': 600 + id: 'a_448326_16c2ada014224bee815a90d2248322f5', + impid: '2a3826aae345f4', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 160, + h: 600 }, { - 'id': 'a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'impid': '3cf96fd26ed4c5', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 300, - 'h': 250 + id: 'a_430392_beac4c1515da4576acf6cb9c5340b40c', + impid: '3cf96fd26ed4c5', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 300, + h: 250 }, ] } ], - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -683,20 +674,19 @@ describe('sovrnBidAdapter', function() { } } }, - 'headers': {} + headers: {} } - ]; + ] it('should return if iid present on server response & iframe syncs enabled', function() { const expectedReturnStatement = { - 'type': 'iframe', - 'url': 'https://ap.lijit.com/beacon?informer=13487408', + type: 'iframe', + url: 'https://ap.lijit.com/beacon?informer=13487408', } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse) - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); - - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include gdpr consent string if present', function() { const gdprConsent = { @@ -704,64 +694,63 @@ describe('sovrnBidAdapter', function() { consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '') - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include us privacy string if present', function() { - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString) - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include all privacy strings if present', function() { const gdprConsent = { gdprApplies: 1, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) - }); + }) it('should not return if iid missing on server response', function() { - const returnStatement = spec.getUserSyncs(syncOptions, []); + const returnStatement = spec.getUserSyncs(syncOptions, []) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should not return if iframe syncs disabled', function() { - const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); + const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should include pixel syncs', function() { const pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true } - const otherResponce = { ...serverResponse, - 'body': { + body: { ...serverResponse.body, - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -778,33 +767,33 @@ describe('sovrnBidAdapter', function() { const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, otherResponce]) - expect(returnStatement.length).to.equal(4); + expect(returnStatement.length).to.equal(4) expect(returnStatement).to.deep.include.members([ { type: 'image', url: 'http://idprovider1.com' }, { type: 'image', url: 'http://idprovider2.com' }, { type: 'image', url: 'http://idprovider3.com' }, { type: 'image', url: 'http://idprovider4.com' } - ]); + ]) }) }) describe('prebid 3 upgrade', function() { const bidRequest = { ...baseBidRequest, - 'params': { - 'tagid': '403370' + params: { + tagid: '403370' }, - 'mediaTypes': { - 'banner': { - 'sizes': [ + mediaTypes: { + banner: { + sizes: [ [300, 250], [300, 600] ] } }, - }; - const request = spec.buildRequests([bidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + } + const request = spec.buildRequests([bidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('gets sizes from mediaTypes.banner', function() { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) @@ -813,8 +802,8 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') }) }) }) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js new file mode 100644 index 00000000000..1178d4062c4 --- /dev/null +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -0,0 +1,453 @@ +import {expect} from 'chai'; +import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; +import {config} from '../../../src/config' +import * as utils from '../../../src/utils' + +describe('Taboola Adapter', function () { + let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; + + beforeEach(() => { + hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + getCookie = sinon.stub(userData.storageManager, 'getCookie'); + getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + + $$PREBID_GLOBAL$$.bidderSettings = { + taboola: { + storageAllowed: true + } + }; + }); + + afterEach(() => { + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + getCookie.restore(); + getDataFromLocalStorage.restore(); + localStorageIsEnabled.restore(); + }) + + const commonBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', + auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', + } + + const displayBidRequestParams = { + sizes: [[300, 250], [300, 600]] + } + + describe('isBidRequestValid', function () { + it('should fail when bid is invalid - tagId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - publisherId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + tagId: 'below the article' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - sizes isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should succeed when bid contains valid', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + }) + + describe('buildRequests', function () { + const defaultBidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/ref', + canonicalUrl: 'https://example.com/' + } + } + + it('should build display request', function () { + const expectedData = { + 'imp': [{ + 'id': 1, + 'banner': { + format: [{ + w: displayBidRequestParams.sizes[0][0], + h: displayBidRequestParams.sizes[0][1] + }, + { + w: displayBidRequestParams.sizes[1][0], + h: displayBidRequestParams.sizes[1][1] + } + ] + }, + 'tagid': commonBidRequest.params.tagId, + 'bidfloor': null, + 'bidfloorcur': 'USD' + }], + 'site': { + 'id': commonBidRequest.params.publisherId, + 'name': commonBidRequest.params.publisherId, + 'domain': window.location.host, + 'page': commonBidderRequest.refererInfo.canonicalUrl, + 'ref': commonBidderRequest.refererInfo.referer, + 'publisher': {'id': commonBidRequest.params.publisherId}, + 'content': {'language': navigator.language} + }, + 'device': {'ua': navigator.userAgent}, + 'source': {'fd': 1}, + 'bcat': [], + 'badv': [], + 'user': { + 'buyeruid': 0, + 'ext': {}, + }, + 'regs': {'coppa': 0, 'ext': {}} + }; + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`); + expect(res.data).to.deep.equal(JSON.stringify(expectedData)); + }) + + it('should pass optional parameters in request', function () { + const optionalParams = { + bidfloor: 0.25, + bidfloorcur: 'EUR' + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].bidfloor).to.deep.equal(0.25); + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); + }); + + it('should pass bidder timeout', function () { + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(500); + }); + + describe('handle privacy segments when building request', function () { + it('should pass GDPR consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + }; + + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + uspConsent: 'consentString' + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.regs.ext.us_privacy).to.equal('consentString'); + }); + + it('should pass coppa consent', function () { + config.setConfig({coppa: true}) + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const resData = JSON.parse(res.data); + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('handle userid ', function () { + it('should get user id from local storage', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(true); + localStorageIsEnabled.returns(true); + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(51525152); + }); + + it('should get user id from cookie if local storage isn`t defined', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.returns('taboola%20global%3Auser-id=12121212'); + + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + + expect(resData.user.buyeruid).to.equal('12121212'); + }); + + it('should get user id from TRC if local storage and cookie isn`t defined', function () { + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + localStorageIsEnabled.returns(false); + + window.TRC = { + user_id: 31313132 + }; + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(window.TRC.user_id); + + delete window.TRC; + }); + + it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + + it('should set buyeruid to be 0 if it`s a new user', function () { + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + }); + }) + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse(serverResponse, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if no server response', function () { + const res = spec.interpretResponse({}, request) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if server response without seatbid', function () { + const overriddenServerResponse = {...serverResponse}; + const seatbid = {...serverResponse.body.seatbid[0]}; + overriddenServerResponse.body.seatbid[0] = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.seatbid[0] = seatbid; + }); + + it('should return empty array if server response without bids', function () { + const overriddenServerResponse = {...serverResponse}; + const bid = [...serverResponse.body.seatbid[0].bid]; + overriddenServerResponse.body.seatbid[0].bid = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.seatbid[0].bid = bid; + }); + + it('should interpret display response', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 360, + netRevenue: false, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + + describe('userData', function () { + // todo: add UT for getUserSyncs + }) + + describe('internal functions', function () { + describe('getPageUrl', function () { + let origPageUrl; + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' + } + }; + + beforeEach(function () { + // remember original pageUrl in config + origPageUrl = config.getConfig('pageUrl'); + + // unset pageUrl in config + config.setConfig({pageUrl: null}); + }); + + afterEach(function () { + // set original pageUrl to config + config.setConfig({pageUrl: origPageUrl}); + }); + + it('should handle empty or missing data', function () { + expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); + expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); + }); + + it('should use "pageUrl" from config', function () { + config.setConfig({pageUrl: 'http://page.url'}); + + expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); + }); + + it('should use bidderRequest.refererInfo.canonicalUrl', function () { + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + + it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + config.setConfig({pageUrl: 'https://page.url'}); + + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + }); + + describe('getReferrer', function () { + it('should handle empty or missing data', function () { + expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); + expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); + }); + + it('should use bidderRequest.refererInfo.referer', () => { + const bidderRequest = { + refererInfo: { + referer: 'foobar' + } + }; + + expect(internal.getReferrer(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.referer); + }); + }); + }) +}) diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 20113a63994..f6faa67f6b8 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,25 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const params = { + user: { + uid: 222, + buyeruid: 333 + }, + tags: { + someTag: 444, + }, + sid: 'publisherId', + shortname: 'test_shortname', + site: { + page: 'testPage' + }, + app: { + bundle: 'testBundle' + }, + test: 1 + }; + const bannerRequest = [{ bidId: 12345, auctionId: 67890, @@ -41,18 +60,7 @@ describe('Zeta Ssp Bid Adapter', function () { consentString: 'consentString' }, uspConsent: 'someCCPAString', - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params, userIdAsEids: eids }]; @@ -72,18 +80,7 @@ describe('Zeta Ssp Bid Adapter', function () { refererInfo: { referer: 'http://www.zetaglobal.com/page?param=video' }, - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params }]; it('Test the bid validation function', function () { @@ -269,4 +266,20 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].banner).to.be.undefined; }); + + it('Test required params in banner request', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.eql('test_shortname'); + }); + + it('Test required params in video request', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.eql('test_shortname'); + }); }); diff --git a/test/test_deps.js b/test/test_deps.js index 8b8c9fd3a0f..77fbad93e1c 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -7,3 +7,4 @@ window.process = { require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); +require('test/mocks/analyticsStub.js');