diff --git a/.eslintrc.js b/.eslintrc.js index 06a5e81d9f5..fc3ad3afe66 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,6 @@ module.exports = { 'import' ], globals: { - '$$PREBID_GLOBAL$$': false, 'BROWSERSTACK_USERNAME': false, 'BROWSERSTACK_KEY': false, 'FEATURES': 'readonly', diff --git a/features.json b/features.json index c0f7e4d75a9..ccb2166a05f 100644 --- a/features.json +++ b/features.json @@ -1,3 +1,4 @@ [ - "NATIVE" + "NATIVE", + "VIDEO" ] diff --git a/integrationExamples/gpt/lemma_sample.html b/integrationExamples/gpt/lemma_sample.html new file mode 100755 index 00000000000..bdf72eeb484 --- /dev/null +++ b/integrationExamples/gpt/lemma_sample.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<html> + + <head> + <meta name='viewport' + content='width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1'> + <style> + html, + body { + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; + } + + #lemmaAd { + height: 100% !important; + max-height: 100%; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + resize: vertical; + margin: 0 auto; + position: relative; + object-fit: fill; + } + </style> + <script> + var PREBID_TIMEOUT = 3300; + + var googletag = googletag || {}; + googletag.cmd = googletag.cmd || []; + + var bannerAdUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [ + [728, 90] + ] + } + }, + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 975, // required + adunitId: '20134', // required + device_type: 2, + } + }] + }]; + var videoAdUnits = [{ + code: 'video1', + sizes: [ + [1920, 1080] + ], + mediaTypes: { + video: { + playerSize: [1920, 1080], // required + context: 'instream' + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 975, // required + adunitId: '20134', // required + latitude: 34.9578, + longitude: -85.3012, + device_type: 3, + ifa: "ae59889b-d5bb-444b-892b-57057463d584", + video: { + minduration: 3, + maxduration: 300, + protocols: [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + skippable: true, + playback_method: ['auto_play_sound_off'], + mimes: ['video/x-flv', + "video/mp4", + "video/mpeg", + "video/ogg", + "video/webm", + "application/javascript"], // required + } + } + }] + }]; + var pbjs = pbjs || {}; + pbjs.que = pbjs.que || []; + </script> + <script async src="../../build/dev/prebid.js"></script> + <script> + pbjs.que.push(function () { + pbjs.setConfig({ + debug: true + }); + pbjs.addAdUnits(videoAdUnits); + pbjs.requestBids({ + timeout: PREBID_TIMEOUT, + bidsBackHandler: function (bids) { + var highestCpmBids = pbjs.getHighestCpmBids('banner-div'); + if (highestCpmBids.length) { + var doc = document.getElementById('banner-div'); + pbjs.renderAd(doc.contentWindow.document, highestCpmBids[0].adId); + } + } + }); + }); + + </script> + </head> + + <body> + <div id='lemmaAd'> + <div id="banner-div"></div> + <iframe id='video1' frameBorder="0"></iframe> + </div> + </body> + +</html> \ No newline at end of file diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index 8d82d50c3e9..142a7c39613 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -6,9 +6,6 @@ <script src="../../build/dev/prebid.js" async type="text/javascript"></script> <script async src="//www.googletagservices.com/tag/js/gpt.js"></script> <script> - var helper = document.getElementById('need-help'); - if (helper) helper.style.display = location.href !== 'http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html' ? 'block' : 'none'; - /* adapted from https://docs.prebid.org/dev-docs/examples/basic-example.html */ var div_1_sizes = [ [300, 250], @@ -197,4 +194,8 @@ <h5>Div-2</h5> </div> </body> + <script type="text/javascript"> + var helper = document.getElementById('need-help'); + if (helper) helper.style.display = location.href !== 'http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html' ? 'block' : 'none'; + </script> </html> \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index bea8b70b4fe..2216d0ed6ae 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -1,6 +1,6 @@ <script> // this script can be returned by an ad server delivering a cross domain iframe, into which the -// creative will be rendered, e.g. DFP delivering a SafeFrame +// creative will be rendered, e.g. GAM delivering a SafeFrame const windowLocation = window.location; const urlParser = document.createElement('a'); diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js index 2057af8b6d3..c367aec268a 100644 --- a/libraries/ortbConverter/converter.js +++ b/libraries/ortbConverter/converter.js @@ -90,12 +90,13 @@ export function ortbConverter({ req: Object.assign({bidRequests}, defaultContext, context), imp: {} } + ctx.req.impContext = ctx.imp; const imps = bidRequests.map(bidRequest => { const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context); const result = buildImp(bidRequest, impContext); if (result != null) { if (result.hasOwnProperty('id')) { - impContext.bidRequest = bidRequest; + Object.assign(impContext, {bidRequest, imp: result}); ctx.imp[result.id] = impContext; return result; } @@ -116,7 +117,7 @@ export function ortbConverter({ throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') } function augmentContext(ctx, extraParams = {}) { - return Object.assign({ortbRequest: request}, extraParams, ctx); + return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx); } const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); const bidResponses = (response.seatbid || []).flatMap(seatbid => diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 1d6bfb8424e..00922608707 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,10 +1,10 @@ -import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; +import {deepSetValue, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; import {fillNativeImp, fillNativeResponse} from './native.js'; import {BID_RESPONSE, IMP, REQUEST} from '../../../src/pbjsORTB.js'; -import {config} from '../../../src/config.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; export const DEFAULT_PROCESSORS = { [REQUEST]: { @@ -15,14 +15,10 @@ export const DEFAULT_PROCESSORS = { mergeDeep(ortbRequest, bidderRequest.ortb2) } }, - // override FPD app, site, and device with getConfig('app'), etc if defined - // TODO: these should be deprecated for v8 - appFpd: fpdFromTopLevelConfig('app'), - siteFpd: fpdFromTopLevelConfig('site'), - deviceFpd: fpdFromTopLevelConfig('device'), onlyOneClient: { + // make sure only one of 'dooh', 'app', 'site' is set in request priority: -99, - fn: onlyOneClientSection + fn: clientSectionChecker('ORTB request') }, props: { // sets request properties id, tmax, test, source.tid @@ -57,10 +53,6 @@ export const DEFAULT_PROCESSORS = { // populates imp.banner fn: fillBannerImp }, - video: { - // populates imp.video - fn: fillVideoImp - }, pbadslot: { // removes imp.ext.data.pbaslot if it's not a string // TODO: is this needed? @@ -82,10 +74,6 @@ export const DEFAULT_PROCESSORS = { // sets banner response attributes if bidResponse.mediaType === BANNER fn: bannerResponseProcessor(), }, - video: { - // sets video response attributes if bidResponse.mediaType === VIDEO - fn: fillVideoResponse - }, props: { // sets base bidResponse properties common to all types of bids fn(bidResponse, bid, context) { @@ -126,28 +114,13 @@ if (FEATURES.NATIVE) { } } -function fpdFromTopLevelConfig(prop) { - return { - priority: 90, // after FPD from 'ortb2', before the rest - fn(ortbRequest) { - const data = config.getConfig(prop); - if (typeof data === 'object') { - ortbRequest[prop] = mergeDeep({}, ortbRequest[prop], data); - } - } +if (FEATURES.VIDEO) { + DEFAULT_PROCESSORS[IMP].video = { + // populates imp.video + fn: fillVideoImp + } + DEFAULT_PROCESSORS[BID_RESPONSE].video = { + // sets video response attributes if bidResponse.mediaType === VIDEO + fn: fillVideoResponse } -} - -export function onlyOneClientSection(ortbRequest) { - ['dooh', 'app', 'site'].reduce((found, section) => { - if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { - if (found != null) { - logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) - delete ortbRequest[section]; - } else { - found = section; - } - } - return found; - }, null); } diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 798ff499206..49f3fdc6e52 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -20,6 +20,7 @@ import { logInfo, logWarn, mergeDeep, + isStr, } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -924,6 +925,46 @@ export const spec = { adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? }; + // Force the Split Keyword to be a String + if (bidRequest.params.splitKeyword) { + if (isStr(bidRequest.params.splitKeyword) || isNumber(bidRequest.params.splitKeyword)) { + bidRequest.params.splitKeyword = bidRequest.params.splitKeyword.toString(); + } else { + delete bidRequest.params.splitKeyword; + + logWarn(LOG_PREFIX, 'The splitKeyword param have been removed because the type is invalid, accepted type: number or string.'); + } + } + + // Force the Data Layer key and value to be a String + if (bidRequest.params.dataLayer) { + if (isStr(bidRequest.params.dataLayer) || isNumber(bidRequest.params.dataLayer) || isArray(bidRequest.params.dataLayer) || isFn(bidRequest.params.dataLayer)) { + logWarn(LOG_PREFIX, 'The dataLayer param is invalid, only object is accepted as a type.'); + delete bidRequest.params.dataLayer; + } else { + let invalidDlParam = false; + + bidRequest.params.dl = bidRequest.params.dataLayer + // Remove the dataLayer from the BidRequest to send the `dl` instead of the `dataLayer` + delete bidRequest.params.dataLayer + + Object.keys(bidRequest.params.dl).forEach((key) => { + if (bidRequest.params.dl[key]) { + if (isStr(bidRequest.params.dl[key]) || isNumber(bidRequest.params.dl[key])) { + bidRequest.params.dl[key] = bidRequest.params.dl[key].toString(); + } else { + invalidDlParam = true; + delete bidRequest.params.dl[key]; + } + } + }); + + if (invalidDlParam) { + logWarn(LOG_PREFIX, 'Some parameters of the dataLayer property have been removed because the type is invalid, accepted type: number or string.'); + } + } + } + Object.keys(features).forEach((prop) => { if (features[prop] === '') { delete features[prop]; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index e65a044c2fa..45f39fc6f2d 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -61,6 +61,8 @@ Below, the list of Adagio params and where they can be set. | debug | | x | | video | | x | | native | | x | +| splitKeyword | | x | +| dataLayer | | x | _* These params are deprecated in favor the Global configuration setup, see below._ @@ -115,6 +117,10 @@ var adUnits = [ context: 1, plcmttype: 2 }, + splitKeyword: 'splitrule-one', + dl: { + placement: '1234' + } } }] } diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index f0a6852b084..d73c25935ee 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -1,4 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'addefend'; @@ -16,7 +17,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let bid = { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, auctionId: false, pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index d8e598deb8f..82bd7f03ff0 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -3,10 +3,9 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {_map, deepAccess, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {deepAccess, deepSetValue, mergeDeep, parseSizesInput, deepClone} from '../src/utils.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const { getConfig } = config; @@ -16,38 +15,7 @@ const BIDDER_ALIAS = [ { code: 'adformOpenRTB', gvlid: GVLID }, { code: 'adform', gvlid: GVLID } ]; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 1, - type: 12, - name: 'data' - } -}; + const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; export const spec = { @@ -61,9 +29,6 @@ export const spec = { return !!(mid || (inv && mname)); }, buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let app, site; const commonFpd = bidderRequest.ortb2 || {}; @@ -123,45 +88,28 @@ export const spec = { } }; - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + if (bid.nativeOrtbRequest && bid.nativeOrtbRequest.assets) { + let assets = bid.nativeOrtbRequest.assets; + let requestAssets = []; + for (let i = 0; i < assets.length; i++) { + let asset = deepClone(assets[i]); + let img = asset.img; + if (img) { + let aspectratios = img.ext && img.ext.aspectratios; + + if (aspectratios) { + let ratioWidth = parseInt(aspectratios[0].split(':')[0], 10); + let ratioHeight = parseInt(aspectratios[0].split(':')[1], 10); + img.wmin = img.wmin || 0; + img.hmin = ratioHeight * img.wmin / ratioWidth | 0; + } } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; + requestAssets.push(asset); } - }).filter(Boolean); - if (assets.length) { imp.native = { request: { - assets + assets: requestAssets } }; } @@ -268,7 +216,9 @@ export const spec = { }; if (bidResponse.native) { - result.native = parseNative(bidResponse); + result.native = { + ortb: bidResponse.native + }; } else { result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; } @@ -286,25 +236,6 @@ export const spec = { registerBidder(spec); -function parseNative(bid) { - const { assets, link, imptrackers, jstracker } = bid.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - - return result; -} - function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { result = deepAccess(collection[i], key); diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 5736c7c19ba..d5639f57c10 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -98,7 +98,8 @@ export const spec = { {code: 'sonic_twist'}, {code: 'displayioads'}, {code: 'rtbdemand_com'}, - {code: 'bidbuddy'} + {code: 'bidbuddy'}, + {code: 'adliveconnect'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/adpod.js b/modules/adpod.js index f1ab4bd2ef1..4ab8e4e5ab9 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -28,14 +28,13 @@ import { import { addBidToAuction, AUCTION_IN_PROGRESS, - callPrebidCache, doCallbacksIfTimedout, getPriceByGranularity, getPriceGranularity } from '../src/auction.js'; import {checkAdUnitSetup} from '../src/prebid.js'; import {checkVideoBidSetup} from '../src/video.js'; -import {module, setupBeforeHookFnOnce} from '../src/hook.js'; +import {getHook, module, setupBeforeHookFnOnce} from '../src/hook.js'; import {store} from '../src/videoCache.js'; import {config} from '../src/config.js'; import {ADPOD} from '../src/mediaTypes.js'; @@ -424,7 +423,7 @@ config.getConfig('adpod', config => adpodSetConfig(config.adpod)); * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. */ function initAdpodHooks() { - setupBeforeHookFnOnce(callPrebidCache, callPrebidCacheHook); + setupBeforeHookFnOnce(getHook('callPrebidCache'), callPrebidCacheHook); setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); } diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a7c83d9c190..ebba4f3ec6b 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -24,6 +24,7 @@ const HOST_GETTERS = { pgam: () => 'ghb.pgamssp.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', vidcrunchllc: () => 'ghb.platform.vidcrunch.com', + '9dotsmedia': () => 'ghb.platform.audiodots.com' } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -39,12 +40,18 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + aliases: [ + 'onefiftytwomedia', + 'appaloosa', + 'bidsxchange', + 'streamkey', + 'janet', { code: 'selectmedia', gvlid: 775 }, { code: 'navelix', gvlid: 380 }, 'pgam', { code: 'ocm', gvlid: 1148 }, { code: 'vidcrunchllc', gvlid: 1145 }, + '9dotsmedia' ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index c39d9e14f17..c7138e43cfe 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,9 +1,10 @@ -import {getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; +import {deepClone, getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; export const BIDDER_CODE = 'aduptech'; +export const GVLID = 647; export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; export const ENDPOINT_URL = 'https://rtb.d.adup-tech.com/prebid/' + ENDPOINT_URL_PUBLISHER_PLACEHOLDER + '_bid'; export const ENDPOINT_METHOD = 'POST'; @@ -20,14 +21,14 @@ export const internal = { * @returns {null|Object.<string, string|boolean>} */ extractGdpr: (bidderRequest) => { - if (bidderRequest && bidderRequest.gdprConsent) { - return { - consentString: bidderRequest.gdprConsent.consentString, - consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true - }; + if (!bidderRequest?.gdprConsent) { + return null; } - return null; + return { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true + }; }, /** @@ -59,30 +60,34 @@ export const internal = { * @returns {null|Object.<string, *>} */ extractBannerConfig: (bidRequest) => { - const sizes = getAdUnitSizes(bidRequest); - if (isArray(sizes) && !isEmpty(sizes)) { - const banner = { sizes: sizes }; + const adUnitSizes = getAdUnitSizes(bidRequest); + if (!isArray(adUnitSizes) || isEmpty(adUnitSizes)) { + return null; + } - // try to add floor for each banner size - banner.sizes.forEach(size => { - const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size }); - if (floor) { - size.push(floor.floor); - size.push(floor.currency); - } - }); + const banner = { sizes: [] }; - // try to add default floor for banner - const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' }); + adUnitSizes.forEach(adUnitSize => { + const size = deepClone(adUnitSize); + + // try to add floor for each banner size + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: adUnitSize }); if (floor) { - banner.floorPrice = floor.floor; - banner.floorCurrency = floor.currency; + size.push(floor.floor); + size.push(floor.currency); } - return banner; + banner.sizes.push(size); + }); + + // try to add default floor for banner + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' }); + if (floor) { + banner.floorPrice = floor.floor; + banner.floorCurrency = floor.currency; } - return null; + return banner; }, /** @@ -92,20 +97,20 @@ export const internal = { * @returns {null|Object.<string, *>} */ extractNativeConfig: (bidRequest) => { - if (bidRequest?.mediaTypes?.native) { - const native = bidRequest.mediaTypes.native; + if (!bidRequest?.mediaTypes?.native) { + return null; + } - // try to add default floor for native - const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' }); - if (floor) { - native.floorPrice = floor.floor; - native.floorCurrency = floor.currency; - } + const native = deepClone(bidRequest.mediaTypes.native); - return native; + // try to add default floor for native + const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' }); + if (floor) { + native.floorPrice = floor.floor; + native.floorCurrency = floor.currency; } - return null; + return native; }, /** @@ -115,11 +120,11 @@ export const internal = { * @returns {null|Object.<string, *>} */ extractParams: (bidRequest) => { - if (bidRequest && bidRequest.params) { - return bidRequest.params + if (!bidRequest?.params) { + return null; } - return null; + return deepClone(bidRequest.params); }, /** @@ -190,6 +195,7 @@ export const internal = { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE], + gvlid: GVLID, /** * Validate given bid request diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index f3bb1270334..21b6c1be783 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; /** * Analytics adapter from adxcg.com @@ -122,7 +123,7 @@ function send (data) { ats: adxcgAnalyticsAdapter.context.auctionTimestamp, aav: adxcgAnalyticsVersion, iob: intersectionObserverAvailable(window) ? '1' : '0', - pbv: $$PREBID_GLOBAL$$.version, + pbv: getGlobal().version, sz: window.screen.width + 'x' + window.screen.height } }); diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 3578cc4b87e..174502d1757 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -8,7 +8,6 @@ import { config } from '../src/config.js'; import { submodule } from '../src/hook.js'; import { - mergeDeep, deepSetValue, deepAccess, } from '../src/utils.js'; @@ -85,13 +84,24 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) { * @param {Array} audiences * @return {{}} a map from bidder code to ORTB2 config */ -export function getAudiencesAsBidderOrtb2(rtdConfig, audiences) { +export function setAudiencesAsBidderOrtb2(rtdConfig, audiences) { const bidders = deepAccess(rtdConfig, 'params.bidders'); - if (!bidders || bidders.length === 0) return {}; - const agOrtb2 = {} - deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); + if (!bidders || bidders.length === 0 || !audiences || audiences.length === 0) return; - return Object.fromEntries(bidders.map(bidder => [bidder, agOrtb2])); + const keywords = audiences.map( + (audienceId) => `perid=${audienceId}` + ).join(','); + + config.mergeBidderConfig({ + bidders: bidders, + config: { + ortb2: { + site: { + keywords, + } + } + } + }) } export function setAudiencesUsingAppNexusAuctionKeywords(audiences) { @@ -131,7 +141,7 @@ export function passAudiencesToBidders( const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { setAudiencesUsingAppNexusAuctionKeywords(audiences); - mergeDeep(bidConfig?.ortb2Fragments?.bidder, getAudiencesAsBidderOrtb2(rtdConfig, audiences)); + setAudiencesAsBidderOrtb2(rtdConfig, audiences) if (adUnits) { setAudiencesToAppNexusAdUnits(adUnits, audiences); } diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 0c0227ea34a..448969c6f29 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -4,6 +4,7 @@ import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; export const storage = getStorageManager(); @@ -352,7 +353,7 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; let events = []; setTimeout(() => { - let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + let winningBids = getGlobal().getAllWinningBids(); logInfo('ATS Analytics - winning bids: ', winningBids) // prepare format data for sending to analytics endpoint if (handlerRequest.length) { diff --git a/modules/browsiBidAdapter.js b/modules/browsiBidAdapter.js new file mode 100644 index 00000000000..f66b7b2c353 --- /dev/null +++ b/modules/browsiBidAdapter.js @@ -0,0 +1,171 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {logError, logInfo, isArray, isStr} from '../src/utils.js'; + +const BIDDER_CODE = 'browsi'; +const DATA = 'brwvidtag'; +const ADAPTER = '__bad'; +const USP_TO_REPLACE = '__USP__'; +const GDPR_STR_TO_REPLACE = '__GDPR_STR__'; +const GDPR_TO_REPLACE = '__GDPR__'; +export const ENDPOINT = 'https://rtb.avantisvideo.com/api/v2/auction/getbid'; + +export const spec = { + code: BIDDER_CODE, + gvlid: 329, + supportedMediaTypes: [VIDEO], + /** + * Determines whether or not the given bid request is valid. + * @param bid + * @returns {boolean} + */ + isBidRequestValid: function (bid) { + if (!bid.params) { + return false; + } + const {pubId, tagId} = bid.params + const {mediaTypes} = bid; + return !!(validateBrowsiIds(pubId, tagId) && mediaTypes?.[VIDEO]); + }, + /** + * Make a server request from the list of BidRequests + * @param validBidRequests + * @param bidderRequest + * @returns ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const requests = []; + const {refererInfo, bidderRequestId, gdprConsent, uspConsent} = bidderRequest; + validBidRequests.forEach(bidRequest => { + const {bidId, adUnitCode, auctionId, transactionId, schain, params} = bidRequest; + const video = getVideoMediaType(bidRequest); + + const request = { + method: 'POST', + url: params.endpoint || ENDPOINT, + data: { + requestId: bidderRequestId, + bidId: bidId, + timeout: getTimeout(bidderRequest), + baData: getData(), + referer: refererInfo.page || refererInfo, + gdpr: gdprConsent, + ccpa: uspConsent, + sizes: video.playerSize, + video: video, + aUCode: adUnitCode, + aID: auctionId, + tID: transactionId, + schain: schain, + params: params + } + }; + requests.push(request); + }) + return requests; + }, + /** + * Unpack the response from the server into a list of bids. + * @param serverResponse A successful response from the server. + * @param request + * @returns {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + const bidResponses = []; + const response = serverResponse?.body; + if (!response) { return bidResponses; } + const { + bidId, + w, + h, + vXml, + vUrl, + cpm, + cur, + ttl, + ...extraParams + } = response; + delete extraParams.userSyncs; + const bidResponse = { + requestId: request.data.bidId, + bidId, + vastXml: vXml, + vastUrl: vUrl, + cpm, + ttl, + mediaType: VIDEO, + width: w, + height: h, + currency: cur, + bidderCode: BIDDER_CODE, + ...extraParams + }; + bidResponses.push(bidResponse); + return bidResponses; + }, + /** + * Extracts user-syncs information from server response + * @param syncOptions {SyncOptions} + * @param serverResponses {ServerResponse[]} + * @param gdprConsent + * @param uspConsent + * @returns {UserSync[]} + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const serverResponse = isArray(serverResponses) ? serverResponses[0] : serverResponses; + const syncParams = serverResponse?.body?.userSyncs; + const userSyncs = []; + const allowedTypes = []; + syncOptions.iframeEnabled && allowedTypes.push('iframe'); + syncOptions.pixelEnabled && allowedTypes.push('image'); + if (syncParams && allowedTypes.length) { + syncParams.forEach(syncParam => { + let { url, type } = syncParam; + if (!allowedTypes.includes(type)) { return; } + url = getValidUrl(url, gdprConsent, uspConsent); + userSyncs.push({ + type, + url + }); + }) + } + return userSyncs; + }, + onTimeout(timeoutData) { + logInfo(`${BIDDER_CODE} bidder timed out`, timeoutData); + }, + onBidderError: function ({error}) { + logError(`${BIDDER_CODE} bidder error`, error); + } +} +/** + * Replaces GdprConsent and uspConsent params in url + * @param url {String} + * @param gdprConsent + * @param uspConsent + * @returns {string} + */ +const getValidUrl = function (url, gdprConsent, uspConsent) { + let validUrl = url.replace(GDPR_TO_REPLACE, gdprConsent?.gdprApplies || '') + .replace(GDPR_STR_TO_REPLACE, encodeURIComponent(gdprConsent?.consentString || '')) + .replace(USP_TO_REPLACE, encodeURIComponent(uspConsent?.consentString || '')); + if (validUrl.indexOf('http') < 0) { + validUrl = 'http://' + validUrl; + } + return validUrl; +} + +const validateBrowsiIds = function (pubId, tagId) { + return pubId && tagId && isStr(pubId) && isStr(tagId); +} +const getData = function () { + return window[DATA]?.[ADAPTER]; +} +const getTimeout = function (bidderRequest) { + return bidderRequest.timeout || config.getConfig('bidderTimeout'); +} +const getVideoMediaType = function (bidRequest) { + return bidRequest.mediaTypes?.[VIDEO]; +} +registerBidder(spec); diff --git a/modules/browsiBidAdapter.md b/modules/browsiBidAdapter.md new file mode 100644 index 00000000000..cd032393c8e --- /dev/null +++ b/modules/browsiBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Browsi Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@browsi.com +``` + +# Description + +Connects to Browsi Ad server for bids. + +Browsi bid adapter supports Video media type. + +**Note:** The bid adapter requires correct setup and approval, including an existing publisher account. + +For more information about [Browsi](https://www.browsi.com), please contact [support@browsi.com](support@browsi.com). + +# Sample Ad Unit: +```javascript +let videoAdUnit = [ +{ + code: 'videoAdUnit', + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'browsi', + params: { + pubId: '117a476f-9791-4a82-80db-4c01c1683db0', // Publisher ID provided by Browsi + tagId: '1' // Tag ID provided by Browsi + } + }] +}]; +``` diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index ba72eec3d95..46253d7af69 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -1,13 +1,34 @@ -import {deepAccess, getDNT, inIframe, isArray, isNumber, logError, logWarn} from '../src/utils.js'; +import { + deepAccess, + deepSetValue, + getDNT, + inIframe, + isArray, + isFn, + isNumber, + isPlainObject, + isStr, + logError, + logWarn +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; +const ENDPOINTS = { + 'cleanmedianet': 'https://bidder.cleanmediaads.com' +}; + +const DEFAULT_TTL = 360; + export const helper = { - getTopWindowDomain: function (url) { - const domainStart = url.indexOf('://') + '://'.length; - return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + getTopFrame: function () { + try { + return window.top === window ? 1 : 0; + } catch (e) { + } + return 0; }, startsWith: function (str, search) { return str.substr(0, search.length) === search; @@ -23,53 +44,50 @@ export const helper = { } } return BANNER; + }, + getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidfloor ? bid.params.bidfloor : null; + } + + let bidFloor = bid.getFloor({ + mediaType: '*', + size: '*', + currency: 'USD' + }); + + if (isPlainObject(bidFloor) && !isNaN(bidFloor.floor) && bidFloor.currency === 'USD') { + return bidFloor.floor; + } + + return null; } }; export const spec = { code: 'cleanmedianet', aliases: [], - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { - return ( - !!bid.params.supplyPartnerId && - typeof bid.params.supplyPartnerId === 'string' && - (typeof bid.params.bidfloor === 'undefined' || - typeof bid.params.bidfloor === 'number') && - (typeof bid.params['adpos'] === 'undefined' || - typeof bid.params['adpos'] === 'number') && - (typeof bid.params['protocols'] === 'undefined' || - Array.isArray(bid.params['protocols'])) && - (typeof bid.params.instl === 'undefined' || - bid.params.instl === 0 || - bid.params.instl === 1) - ); + return !!bid.params.supplyPartnerId && isStr(bid.params.supplyPartnerId) && + (!bid.params['rtbEndpoint'] || isStr(bid.params['rtbEndpoint'])) && + (!bid.params.bidfloor || isNumber(bid.params.bidfloor)) && + (!bid.params['adpos'] || isNumber(bid.params['adpos'])) && + (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) && + (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1); }, buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { - const { - adUnitCode, - auctionId, - mediaTypes, - params, - sizes, - transactionId - } = bidRequest; - const baseEndpoint = 'https://bidder.cleanmediaads.com'; - const rtbEndpoint = - `${baseEndpoint}/r/${ - params.supplyPartnerId - }/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + - (params.query ? '&' + params.query : ''); - let url = bidderRequest.refererInfo.page; - + const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; + const baseEndpoint = (params['rtbEndpoint'] || ENDPOINTS['cleanmedianet']).replace(/^http:/, 'https:'); + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); const rtbBidRequest = { id: auctionId, site: { domain: bidderRequest.refererInfo.domain, - page: url, + page: bidderRequest.refererInfo.page, ref: bidderRequest.refererInfo.ref }, device: { @@ -81,44 +99,35 @@ export const spec = { }, imp: [], ext: {}, - user: { - ext: {} - } + user: {ext: {}}, + source: {ext: {}}, + regs: {ext: {}} }; - if ( - bidderRequest.gdprConsent && - bidderRequest.gdprConsent.consentString && - bidderRequest.gdprConsent.gdprApplies - ) { - rtbBidRequest.ext.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - rtbBidRequest.regs = { - ext: { - gdpr: bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0 - } - }; - rtbBidRequest.user = { - ext: { - consent: bidderRequest.gdprConsent.consentString - } - }; + const gdprConsent = getGdprConsent(bidderRequest); + rtbBidRequest.ext.gdpr_consent = gdprConsent; + deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.consent_required === true ? 1 : 0); + deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consent_string); + + if (validBidRequests[0].schain) { + deepSetValue(rtbBidRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(rtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } const imp = { id: transactionId, - instl: deepAccess(bidRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, + instl: deepAccess(bidderRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, tagid: adUnitCode, - bidfloor: 0, + bidfloor: helper.getBidFloor(bidRequest) || 0, bidfloorcur: 'USD', secure: 1 }; const hasFavoredMediaType = - params.favoredMediaType && - includes(this.supportedMediaTypes, params.favoredMediaType); + params.favoredMediaType && includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { @@ -126,7 +135,7 @@ export const spec = { banner: { w: sizes.length ? sizes[0][0] : 300, h: sizes.length ? sizes[0][1] : 250, - pos: params.pos || 0, + pos: deepAccess(bidderRequest, 'mediaTypes.banner.pos') || params.pos || 0, topframe: inIframe() ? 0 : 1 } }); @@ -136,15 +145,25 @@ export const spec = { if (mediaTypes && mediaTypes.video) { if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { - let videoImp = { + const playerSize = mediaTypes.video.playerSize || sizes; + const videoImp = Object.assign({}, imp, { video: { - protocols: params.protocols || [1, 2, 3, 4, 5, 6], - pos: params.pos || 0, - ext: {context: mediaTypes.video.context} + protocols: bidRequest.mediaTypes.video.protocols || params.protocols || [1, 2, 3, 4, 5, 6], + pos: deepAccess(bidRequest, 'mediaTypes.video.pos') || params.pos || 0, + ext: { + context: mediaTypes.video.context + }, + mimes: bidRequest.mediaTypes.video.mimes, + maxduration: bidRequest.mediaTypes.video.maxduration, + api: bidRequest.mediaTypes.video.api, + skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, + placement: bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement, + minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, + playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, + startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay } - }; + }); - let playerSize = mediaTypes.video.playerSize || sizes; if (isArray(playerSize[0])) { videoImp.video.w = playerSize[0][0]; videoImp.video.h = playerSize[0][1]; @@ -156,11 +175,20 @@ export const spec = { videoImp.video.h = 250; } - videoImp = Object.assign({}, imp, videoImp); rtbBidRequest.imp.push(videoImp); } } + let eids = []; + if (bidRequest && bidRequest.userId) { + addExternalUserId(eids, deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + addExternalUserId(eids, deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 'idl'); + } + if (eids.length > 0) { + rtbBidRequest.user.ext.eids = eids; + } + if (rtbBidRequest.imp.length === 0) { return; } @@ -181,10 +209,7 @@ export const spec = { return []; } - const bids = response.seatbid.reduce( - (acc, seatBid) => acc.concat(seatBid.bid), - [] - ); + const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); let outBids = []; bids.forEach(bid => { @@ -193,75 +218,81 @@ export const spec = { cpm: bid.price, width: bid.w, height: bid.h, - ttl: 360, + ttl: DEFAULT_TTL, creativeId: bid.crid || bid.adid, netRevenue: true, currency: bid.cur || response.cur, - mediaType: helper.getMediaType(bid) + mediaType: helper.getMediaType(bid), }; - if ( - deepAccess( - bidRequest.bidRequest, - 'mediaTypes.' + outBid.mediaType - ) - ) { + if (bid.adomain && bid.adomain.length) { + outBid.meta = { + advertiserDomains: bid.adomain + } + } + + if (deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { if (outBid.mediaType === BANNER) { outBids.push(Object.assign({}, outBid, {ad: bid.adm})); } else if (outBid.mediaType === VIDEO) { - const context = deepAccess( - bidRequest.bidRequest, - 'mediaTypes.video.context' - ); - outBids.push( - Object.assign({}, outBid, { - vastUrl: bid.ext.vast_url, - vastXml: bid.adm, - renderer: - context === 'outstream' - ? newRenderer(bidRequest.bidRequest, bid) - : undefined - }) - ); + const context = deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context'); + outBids.push(Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined + })); } } }); return outBids; }, - getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; - const gdprApplies = - gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' - ? gdprConsent.gdprApplies - : false; - const suffix = gdprApplies - ? 'gc=' + encodeURIComponent(gdprConsent.consentString) - : 'gc=missing'; + let gdprApplies = false; + let consentString = ''; + let uspConsentString = ''; + + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + gdprApplies = gdprConsent.gdprApplies; + } + let gdpr = gdprApplies ? 1 : 0; + + if (gdprApplies && gdprConsent.consentString) { + consentString = encodeURIComponent(gdprConsent.consentString); + } + + if (uspConsent) { + uspConsentString = encodeURIComponent(uspConsent); + } + + const macroValues = { + gdpr: gdpr, + consent: consentString, + uspConsent: uspConsentString + }; + serverResponses.forEach(resp => { if (resp.body) { const bidResponse = resp.body; if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { - bidResponse.ext['utrk'].forEach(pixel => { - const url = - pixel.url + - (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); - return syncs.push({type: pixel.type, url}); - }); + bidResponse.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); } + if (Array.isArray(bidResponse.seatbid)) { bidResponse.seatbid.forEach(seatBid => { if (Array.isArray(seatBid.bid)) { seatBid.bid.forEach(bid => { if (bid.ext && Array.isArray(bid.ext['utrk'])) { - bid.ext['utrk'].forEach(pixel => { - const url = - pixel.url + - (pixel.url.indexOf('?') > 0 - ? '&' + suffix - : '?' + suffix); - return syncs.push({type: pixel.type, url}); - }); + bid.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); } }); } @@ -269,18 +300,16 @@ export const spec = { } } }); + return syncs; } }; function newRenderer(bidRequest, bid, rendererOptions = {}) { const renderer = Renderer.install({ - url: - (bidRequest.params && bidRequest.params.rendererUrl) || - (bid.ext && bid.ext.renderer_url) || - 'https://s.wlplayer.com/video/latest/renderer.js', + url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || 'https://s.gamoshi.io/video/latest/renderer.js', config: rendererOptions, - loaded: false + loaded: false, }); try { renderer.setRender(renderOutstream); @@ -300,10 +329,9 @@ function renderOutstream(bid) { width: bid.width, height: bid.height, events: { - ALL_ADS_COMPLETED: () => - window.setTimeout(() => { - window['GamoshiPlayer'].removeAd(unitId); - }, 300) + ALL_ADS_COMPLETED: () => window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) }, vastUrl: bid.vastUrl, vastXml: bid.vastXml @@ -311,4 +339,41 @@ function renderOutstream(bid) { }); } +function addExternalUserId(eids, value, source, rtiPartner) { + if (isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + ext: { + rtiPartner + } + }] + }); + } +} + +function replaceMacros(url, macros) { + return url + .replace('[GDPR]', macros.gdpr) + .replace('[CONSENT]', macros.consent) + .replace('[US_PRIVACY]', macros.uspConsent); +} + +function getGdprConsent(bidderRequest) { + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + return { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + } + + return { + consent_required: false, + consent_string: '', + }; +} + registerBidder(spec); diff --git a/modules/cleanmedianetBidAdapter.md b/modules/cleanmedianetBidAdapter.md index f2bc8feb0f0..ee4e049e8d6 100644 --- a/modules/cleanmedianetBidAdapter.md +++ b/modules/cleanmedianetBidAdapter.md @@ -1,45 +1,49 @@ # Overview ``` -Module Name: Clean Media Net Adapter +Module Name: CleanMedia Bid Adapter Module Type: Bidder Adapter -Maintainer: dev@cleanmedia.net +Maintainer: dev@CleanMedia.net ``` # Description -Connects to Clean Media Net's Programmatic advertising platform as a service. +Connects to CleanMedia's Programmatic advertising platform as a service. -Clean Media bid adapter supports Banner & Video (Instream and Outstream). -The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. +CleanMedia bid adapter supports Banner & Outstream Video. The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. # Test Parameters ``` var adUnits = [ - // Banner adUnit + + // Banner adUnit { code: 'banner-div', sizes: [[300, 250]], bids: [{ bidder: 'cleanmedianet', params: { - // ID of the supply partner you created in the Clean Media Net dashboard + + // ID of the supply partner you created in the CleanMedia dashboard supplyPartnerId: '1253', - // OPTIONAL: custom bid floor + + // OPTIONAL: custom bid floor bidfloor: 0.01, - // OPTIONAL: if you know the ad position on the page, specify it here + + // OPTIONAL: if you know the ad position on the page, specify it here // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) //adpos: 1, - // OPTIONAL: whether this is an interstitial placement (0 or 1) + + // OPTIONAL: whether this is an interstitial placement (0 or 1) // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) //instl: 0 } }] }, - // Video outstream adUnit + + // Video outstream adUnit { code: 'video-outstream', - sizes: [[300, 250]], mediaTypes: { video: { context: 'outstream', @@ -47,20 +51,62 @@ var adUnits = [ } }, bids: [ { - bidder: 'cleanmedianet', + bidder: 'CleanMedia', params: { - // ID of the supply partner you created in the dashboard + + // ID of the supply partner you created in the dashboard supplyPartnerId: '1254', - // OPTIONAL: custom bid floor + + // OPTIONAL: custom bid floor bidfloor: 0.01, - // OPTIONAL: if you know the ad position on the page, specify it here + + // OPTIONAL: if you know the ad position on the page, specify it here // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) //adpos: 1, - // OPTIONAL: whether this is an interstitial placement (0 or 1) + + // OPTIONAL: whether this is an interstitial placement (0 or 1) // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) //instl: 0 } }] - } + }, + + // Multi-Format adUnit + { + code: 'banner-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + }, + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'CleanMedia', + params: { + + // ID of the supply partner you created in the CleanMedia dashboard + supplyPartnerId: '1253', + + // OPTIONAL: custom bid floor + bidfloor: 0.01, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0, + + // OPTIONAL: enable enforcement bids of a specific media type (video, banner) + // in this ad placement + // query: 'key1=value1&k2=value2', + // favoredMediaType: 'video', + } + }] + }, ]; ``` diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index dcea60d5231..176729dd607 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; -const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; export const spec = { code: BIDDER_CODE, @@ -47,10 +46,18 @@ export const spec = { optedOut: hasOptedOutOfPersonalization(), adapterVersion: '1.1.1', uspConsent: bidderRequest.uspConsent, - gdprConsent: bidderRequest.gdprConsent + gdprConsent: bidderRequest.gdprConsent, + gppConsent: bidderRequest.gppConsent, } }; + if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + payload.meta.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); const adUnitElement = document.getElementById(bidRequest.adUnitCode) @@ -124,38 +131,6 @@ export const spec = { return bidResponses; }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @param {gdprConsent} object GDPR consent object. - * @param {uspConsent} string US Privacy String. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { - let params = []; - - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); - } - if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - if (uspConsent && (typeof uspConsent === 'string')) { - params.push(`usp_consent=${uspConsent}`); - } - - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') - }); - } - return syncs; - }, - /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -229,16 +204,24 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We can't easily test GDPR consent, without the - * `consent-string` npm module; so will have to rely on that - * happening on the bid-server. */ - const uspConsent = !(bidderRequest?.uspConsent === 'string' && + let uspConsentAllows = true; + + // if a us privacy string was provided, but they explicitly opted out + if ( + typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out + ) { + uspConsentAllows = false; + } - const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + /* + * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + * Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module; + */ + const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent); - return (uspConsent || gdprConsent); + return (uspConsentAllows && gdprConsentAllows); } function collectEid(eids, bid) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 44e8d39b832..6c22b3b9da4 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -11,6 +11,7 @@ import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -287,11 +288,6 @@ function processCmpData(consentObject, {onSuccess, onError}) { ); } - // do extra things for static config - if (userCMP === 'static') { - consentObject = consentObject.getTCData; - } - if (checkData()) { onError(`CMP returned unexpected value during lookup process.`, consentObject); } else { @@ -362,13 +358,17 @@ export function setConsentConfig(config) { if (userCMP === 'static') { if (isPlainObject(config.consentData)) { staticConsentData = config.consentData; + if (staticConsentData?.getTCData != null) { + // accept static config with or without `getTCData` - see https://github.com/prebid/Prebid.js/issues/9581 + staticConsentData = staticConsentData.getTCData; + } consentTimeout = 0; } else { logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); } } if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; gdprDataHandler.enable(); diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 8a9c3f999f0..3b0e1c25c6a 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -10,6 +10,7 @@ import {gppDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import { enrichFPD } from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -362,7 +363,7 @@ export function setConsentConfig(config) { logInfo('consentManagement.gpp module has been activated...'); if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; gppDataHandler.enable(); diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 41a8200c6ea..c2ffba8bb48 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -142,6 +142,10 @@ export const spec = { } if (bidderRequest) { + if (bidderRequest.timeout) { + deepSetValue(payload, 'tmax', bidderRequest.timeout); + } + // Add GDPR flag and consent string if (bidderRequest.gdprConsent) { userExt.consent = bidderRequest.gdprConsent.consentString; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 7f2d6f2a3fb..e6564ca19d6 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -233,7 +233,7 @@ export const spec = { bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); } if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); } if (slot.native) { if (bidRequest.params.nativeCallback) { diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 71c9c7aa341..3394fd8b3f4 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -12,6 +12,7 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; +import {getRefererInfo} from '../src/refererDetection.js'; /** * @typedef {Object} DfpVideoParams @@ -52,6 +53,10 @@ const defaultParamConstants = { export const adpodUtils = {}; +export const dep = { + ri: getRefererInfo +} + /** * Merge all the bid data and publisher-supplied options into a single URL, and then return it. * @@ -259,14 +264,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { * @return {string | undefined} The encoded vast url if it exists, or undefined */ function getDescriptionUrl(bid, components, prop) { - if (config.getConfig('cache.url')) { return; } - - if (!deepAccess(components, `${prop}.description_url`)) { - const vastUrl = bid && bid.vastUrl; - if (vastUrl) { return encodeURIComponent(vastUrl); } - } else { - logError(`input cannnot contain description_url`); - } + return deepAccess(components, `${prop}.description_url`) || dep.ri().page; } /** @@ -293,6 +291,8 @@ function getCustParams(bid, options, urlCustParams) { allTargetingData, adserverTargeting, ); + + // TODO: WTF is this? just firing random events, guessing at the argument, hoping noone notices? events.emit(CONSTANTS.EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); // merge the prebid + publisher targeting sets diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 8145c9d25e7..7930efc4fa8 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -244,7 +244,9 @@ function getItems(validBidRequests, bidderRequest) { } } if (!matchSize) { - return {}; + matchSize = sizes[0] + ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } + : { h: 0, w: 0 }; } ret = { id: id, @@ -253,6 +255,7 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: {}, tagid: globals['tagid'], @@ -294,6 +297,7 @@ function getParam(validBidRequests, bidderRequest) { const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { @@ -312,6 +316,7 @@ function getParam(validBidRequests, bidderRequest) { }, ext: { eids, + firstPartyData, }, user: { buyeruid: getUserID(), diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index c3c6597dd1b..d46cc8ee309 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -2,12 +2,14 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {getWindowFromDocument, logWarn} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; const ADAPTER_VERSION = '1.1.0'; const BIDDER_CODE = 'displayio'; const BID_TTL = 300; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const DEFAULT_CURRENCY = 'USD'; +const US_KEY = '_dio_us'; export const spec = { code: BIDDER_CODE, @@ -67,18 +69,26 @@ export const spec = { function getPayload (bid, bidderRequest) { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { - let r = Math.random() * 16 | 0; - let v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + const storage = getStorageManager({bidderCode: BIDDER_CODE}); + const userSession = (() => { + let us = storage.getDataFromLocalStorage(US_KEY); + if (!us) { + us = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + storage.setDataInLocalStorage(US_KEY, us); + } + return us + })(); const { params, adUnitCode, bidId } = bid; const { siteId, placementId, renderURL, pageCategory, keywords } = params; const { refererInfo, uspConsent, gdprConsent } = bidderRequest; - const mediation = {consent: '-1', gdpr: '-1'}; + const mediation = {gdprConsent: '', gdpr: '-1'}; if (gdprConsent && 'gdprApplies' in gdprConsent) { if (gdprConsent.consentString !== undefined) { - mediation.consent = gdprConsent.consentString; + mediation.gdprConsent = gdprConsent.consentString; } if (gdprConsent.gdprApplies !== undefined) { mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; @@ -110,7 +120,7 @@ function getPayload (bid, bidderRequest) { dnt: window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false, iabConsent: {}, mediation: { - consent: mediation.consent, + gdprConsent: mediation.gdprConsent, gdpr: mediation.gdpr, } }, diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js index 005dd3e67d6..7a2038ed3f0 100644 --- a/modules/distroscaleBidAdapter.js +++ b/modules/distroscaleBidAdapter.js @@ -132,6 +132,24 @@ export const spec = { // TODO: does the fallback to window.location make sense? var pageUrl = bidderRequest?.refererInfo?.page || window.location.href; + // check if dstag is already loaded in ancestry tree + var dsloaded = 0; + try { + var win = window; + while (true) { + if (win.vx.cs_loaded) { + dsloaded = 1; + } + if (win != win.parent) { + win = win.parent; + } else { + break; + } + } + } catch (error) { + // ignore exception + } + var payload = { id: '' + (new Date()).getTime(), at: AUCTION_TYPE, @@ -149,7 +167,9 @@ export const spec = { }, imp: [], user: {}, - ext: {} + ext: { + dsloaded: dsloaded + } }; validBidRequests.forEach(b => { diff --git a/modules/express.js b/modules/express.js index 0b1780e3c26..a2998baed07 100644 --- a/modules/express.js +++ b/modules/express.js @@ -1,6 +1,8 @@ import { logMessage, logWarn, logError, logInfo } from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const MODULE_NAME = 'express'; +const pbjsInstance = getGlobal(); /** * Express Module @@ -12,7 +14,7 @@ const MODULE_NAME = 'express'; * * @param {Object[]} [adUnits = pbjs.adUnits] - an array of adUnits for express to operate on. */ -$$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { +pbjsInstance.express = function(adUnits = pbjsInstance.adUnits) { logMessage('loading ' + MODULE_NAME); if (adUnits.length === 0) { @@ -138,10 +140,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; @@ -168,10 +170,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 0f7092baf03..f29ce7508d5 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js'; +import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; const MODULE = 'fledgeForGpt' @@ -21,22 +22,20 @@ export function init(cfg) { getHook('addComponentAuction').before(addComponentAuctionHook); isEnabled = true; } - logInfo(MODULE, `isEnabled`, cfg); + logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg); } else { if (isEnabled) { getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); isEnabled = false; } - logInfo(MODULE, `isDisabled`, cfg); + logInfo(`${MODULE} disabled`, cfg); } } -export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig) { +export function addComponentAuctionHook(next, adUnitCode, componentAuctionConfig) { const seller = componentAuctionConfig.seller; - const adUnitCode = bidRequest.adUnitCode; const gptSlot = getGptSlotForAdUnitCode(adUnitCode); if (gptSlot && gptSlot.setConfig) { - delete componentAuctionConfig.bidId; gptSlot.setConfig({ componentAuction: [{ configKey: seller, @@ -48,5 +47,54 @@ export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`); } - next(bidRequest, componentAuctionConfig); + next(adUnitCode, componentAuctionConfig); } + +function isFledgeSupported() { + return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator +} + +export function markForFledge(next, bidderRequests) { + if (isFledgeSupported()) { + bidderRequests.forEach((req) => { + req.fledgeEnabled = config.runWithBidder(req.bidderCode, () => config.getConfig('fledgeEnabled')) + }) + } + next(bidderRequests); +} +getHook('makeBidRequests').after(markForFledge); + +export function setImpExtAe(imp, bidRequest, context) { + if (!context.bidderRequest.fledgeEnabled) { + delete imp.ext?.ae; + } +} +registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); + +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { + const impCtx = context.impContext[cfg.impid]; + if (!impCtx?.imp?.ext?.ae) { + logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + } else { + impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; + impCtx.fledgeConfigs.push(cfg); + } + }) +} +registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); + +// ...then, make them available in the adapter's response. This is the client side version, for which the +// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} + +export function setResponseFledgeConfigs(response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({bidId: impCtx.bidRequest.bidId, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } +} +registerOrtbProcessor({type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, fn: setResponseFledgeConfigs, dialects: [PBS]}) diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index 004d44e6737..97b9847eae4 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -1,4 +1,5 @@ -import { _each, isEmpty } from '../src/utils.js'; +import { _each, deepSetValue, isEmpty } from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'fluct'; @@ -39,13 +40,12 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const serverRequests = []; - // TODO: is 'page' the right value here? - const referer = bidderRequest.refererInfo.page; + const page = bidderRequest.refererInfo.page; _each(validBidRequests, (request) => { const data = Object(); - data.referer = referer; + data.page = page; data.adUnitCode = request.adUnitCode; data.bidId = request.bidId; data.transactionId = request.transactionId; @@ -53,6 +53,21 @@ export const spec = { eids: (request.userIdAsEids || []).filter((eid) => SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1) }; + if (bidderRequest.gdprConsent) { + deepSetValue(data, 'regs.gdpr', { + consent: bidderRequest.gdprConsent.consentString, + gdprApplies: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + }); + } + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.us_privacy', { + consent: bidderRequest.uspConsent, + }); + } + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + data.sizes = []; _each(request.sizes, (size) => { data.sizes.push({ @@ -141,8 +156,22 @@ export const spec = { * */ getUserSyncs: (syncOptions, serverResponses) => { - return []; - }, + // gdpr, us_privacy, and coppa params to be handled on the server end. + const usersyncs = serverResponses.reduce((acc, serverResponse) => [ + ...acc, + ...(serverResponse.body.usersyncs ?? []), + ], []); + const syncs = usersyncs.filter( + (sync) => ( + (sync['type'] === 'image' && syncOptions.pixelEnabled) || + (sync['type'] === 'iframe' && syncOptions.iframeEnabled) + ) + ).map((sync) => ({ + type: sync.type, + url: sync.url, + })); + return syncs; + } }; registerBidder(spec); diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 1a962fd4060..c5fb813540e 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -350,6 +350,7 @@ export const spec = { timestamp: timeInMillis, _fw_bidfloor: (bidfloor > 0) ? bidfloor : 0, _fw_bidfloorcur: (bidfloor > 0) ? getFloorCurrency(config) : '', + pbjs_version: '$prebid.version$', pKey: keyCode }; @@ -381,6 +382,14 @@ export const spec = { } } + if (currentBidRequest.userIdAsEids && currentBidRequest.userIdAsEids.length > 0) { + try { + requestParams._fw_prebid_3p_UID = JSON.stringify(currentBidRequest.userIdAsEids); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the userIdAsEids: ' + error); + } + } + var vastParams = currentBidRequest.params.vastUrlParams; if (typeof vastParams === 'object') { for (var key in vastParams) { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index ce7fcc680dd..31bb350aaa6 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -83,7 +83,6 @@ export const spec = { return null; } let pageKeywords = null; - let jwpseg = null; let content = null; let schain = null; let userIdAsEids = null; @@ -91,7 +90,7 @@ export const spec = { let userExt = null; let endpoint = null; let forceBidderName = false; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo, gppConsent} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; const tmax = timeout || config.getConfig('bidderTimeout'); @@ -128,16 +127,11 @@ export const spec = { endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; } const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; - const { pubdata, secid, pubid, source, content: bidParamsContent } = bid.params; + const { secid, pubid, source, content: bidParamsContent } = bid.params; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } + if (jwTargeting && !content && jwTargeting.content) { + content = jwTargeting.content; } let impObj = { @@ -210,23 +204,12 @@ export const spec = { request.site.publisher = { id: pubid }; } - const reqJwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); - const siteContent = bidParamsContent || (jwTargeting && jwTargeting.content); if (siteContent) { request.site.content = siteContent; } - if (reqJwpseg && reqJwpseg.length) { - request.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(reqJwpseg, 'jwpseg'), - }] - }; - } - requests.push(request); sources.push(source); bidsArray.push(bidObject); @@ -274,28 +257,16 @@ export const spec = { mainRequest.site.content = content; } - if (jwpseg && jwpseg.length) { - mainRequest.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - [...requests, mainRequest].forEach((request) => { if (!request) { return; } - if (request.user) user = request.user; + user = null; const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - if (!user) user = { data: [] }; - user = mergeDeep(user, { - data: [...ortb2UserData] - }); + user = { data: [...ortb2UserData] }; } if (gdprConsent && gdprConsent.consentString) { @@ -369,11 +340,22 @@ export const spec = { } }; } + const ortb2Regs = deepAccess(bidderRequest, 'ortb2.regs') || {}; + if (gppConsent || ortb2Regs?.gpp) { + const gpp = { + gpp: gppConsent?.gppString ?? ortb2Regs?.gpp, + gpp_sid: gppConsent?.applicableSections ?? ortb2Regs?.gpp_sid + }; + request.regs = mergeDeep(request?.regs ?? {}, gpp); + } if (uspConsent) { if (!request.regs) { request.regs = {ext: {}}; } + if (!request.regs.ext) { + request.regs.ext = {}; + } request.regs.ext.us_privacy = uspConsent; } @@ -650,22 +632,6 @@ function getUserIdFromFPDStorage() { return storage.getDataFromLocalStorage(USER_ID_KEY) || makeNewUserIdInFPDStorage(); } -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - function reformatKeywords(pageKeywords) { const formatedPageKeywords = {}; Object.keys(pageKeywords).forEach((name) => { diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index c49b7619c07..41689aeeb55 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -90,13 +90,12 @@ export const spec = { auctionId = bid.auctionId; } const { - params: { floorcpm, pubdata, source, secid, pubid, content, video }, + params: { floorcpm, source, secid, pubid, content, video }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp, sizes } = bid; const bidFloor = _getFloor(mediaTypes || {}, bid, isNumber(floorcpm) && floorcpm); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - const jwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); const siteContent = content || (jwTargeting && jwTargeting.content); @@ -156,15 +155,6 @@ export const spec = { request.site.content = siteContent; } - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - if (gdprConsent && gdprConsent.consentString) { userExt = { consent: gdprConsent.consentString }; } @@ -429,20 +419,4 @@ export function getSyncUrl() { return SYNC_URL; } -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - registerBidder(spec); diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index b12aa3ee861..e9bdc955c1e 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -264,7 +264,8 @@ function getEids(userId) { const idProperties = [ 'uid', 'eid', - 'lipbid' + 'lipbid', + 'envelope' ]; return Object.keys(userId).reduce(function (eids, provider) { @@ -391,12 +392,12 @@ function buildRequests(validBidRequests, bidderRequest) { if (gppConsent) { data.gppConsent = { gppString: bidderRequest.gppConsent.gppString, - applicableSections: bidderRequest.gppConsent.applicableSections + gpp_sid: bidderRequest.gppConsent.applicableSections } } else if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) { data.gppConsent = { gppString: bidderRequest.ortb2.regs.gpp, - applicableSections: bidderRequest.ortb2.regs.gpp_sid + gpp_sid: bidderRequest.ortb2.regs.gpp_sid }; } if (coppa) { diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 0bd4e6f8344..7a0299fc427 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -18,7 +18,7 @@ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; -const HADRON_SEGMENT_URL = 'https://seg.hadron.ad.gt/api/v1/rtd'; +const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index d35cdf29fca..d0f3198e03d 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; import * as events from '../src/events.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const { EVENTS: { @@ -34,7 +35,7 @@ const FLUSH_EVENTS = [ const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' const TZ = new Date().getTimezoneOffset(); -const PBJS_VERSION = $$PREBID_GLOBAL$$.version; +const PBJS_VERSION = getGlobal().version; const ID5_REDACTED = '__ID5_REDACTED__'; const isArray = Array.isArray; diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 360a373ba9a..a00fd90506a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -833,7 +833,7 @@ function applyRegulations(r, bidderRequest) { if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { r.user.ext.consented_providers_settings = { - consented_providers: gdprConsent.addtlConsent + addtl_consent: gdprConsent.addtlConsent }; } } diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 6a1c1cf94b0..b3d5bc2af64 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -7,6 +7,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; +const PREBID_VERSION = '$prebid.version$'; const SYNC_COUNT = 5; const GVLID = 972; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; @@ -58,7 +59,8 @@ export const spec = { width: window.screen.width, height: window.screen.height }, - prebidRawBidRequests: validBidRequests + prebidRawBidRequests: validBidRequests, + prebidVersion: PREBID_VERSION }, spec._getAllMetadata(bidderRequest, tdid)); // User Agent Client Hints / SUA diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 821b3263597..26c0d871a12 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -116,6 +116,12 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; diff --git a/modules/kulturemediaBidAdapter.js b/modules/kulturemediaBidAdapter.js new file mode 100644 index 00000000000..0acdd6406cb --- /dev/null +++ b/modules/kulturemediaBidAdapter.js @@ -0,0 +1,472 @@ +import { + deepSetValue, + logInfo, + deepAccess, + logError, + isFn, + isPlainObject, + isStr, + isNumber, + isArray, logMessage +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'kulturemedia'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_NETWORK_ID = 1; +const OPENRTB_VIDEO_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +export const spec = { + code: BIDDER_CODE, + VERSION: '1.0.0', + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: 'https://ads.kulture.media/pbjs', + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return ( + _validateParams(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + + // We need to refactor this to support mixed content when there are both + // banner and video bid requests + let openrtbRequest; + if (hasBannerMediaType(validBidRequests[0])) { + openrtbRequest = buildBannerRequestData(validBidRequests, bidderRequest); + } else if (hasVideoMediaType(validBidRequests[0])) { + openrtbRequest = buildVideoRequestData(validBidRequests[0], bidderRequest); + } + + // adding schain object + if (validBidRequests[0].schain) { + deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // EIDS + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + deepSetValue(openrtbRequest, 'user.ext.eids', eids); + } + + let publisherId = validBidRequests[0].params.publisherId; + let placementId = validBidRequests[0].params.placementId; + const networkId = validBidRequests[0].params.networkId || DEFAULT_NETWORK_ID; + + if (validBidRequests[0].params.e2etest) { + logMessage('E2E test mode enabled'); + publisherId = 'e2etest' + } + let baseEndpoint = spec.ENDPOINT + '?pid=' + publisherId; + + if (placementId) { + baseEndpoint += '&placementId=' + placementId + } + if (networkId) { + baseEndpoint += '&nId=' + networkId + } + + const payloadString = JSON.stringify(openrtbRequest); + return { + method: 'POST', + url: baseEndpoint, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // response is always one seat (exchange) with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + if (bid.adm && bid.price) { + bidResponses.push(_createBidResponse(bid)); + } + }) + } else { + logInfo('kulturemedia.interpretResponse :: no valid responses to interpret'); + } + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + logInfo('kulturemedia.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + if (!syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type !== 'iframe') + } + if (!syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type !== 'image') + } + } + }); + logInfo('kulturemedia.getUserSyncs result=%o', syncs); + return syncs; + }, + +}; + +/* ======================================= + * Util Functions + *======================================= */ + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +function _validateParams(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.e2etest) { + return true; + } + + if (!bidRequest.params.publisherId) { + logError('Validation failed: publisherId not declared'); + return false; + } + + if (!bidRequest.params.placementId) { + logError('Validation failed: placementId not declared'); + return false; + } + + const mediaTypesExists = hasVideoMediaType(bidRequest) || hasBannerMediaType(bidRequest); + if (!mediaTypesExists) { + return false; + } + + return true; +} + +/** + * Validates banner bid request. If it is not banner media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateBanner(bidRequest) { + // If there's no banner no need to validate + if (!hasBannerMediaType(bidRequest)) { + return true; + } + const banner = deepAccess(bidRequest, 'mediaTypes.banner'); + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; +} + +/** + * Validates video bid request. If it is not video media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateVideo(bidRequest) { + // If there's no video no need to validate + if (!hasVideoMediaType(bidRequest)) { + return true; + } + + const videoPlacement = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + const params = deepAccess(bidRequest, 'params', {}); + + if (params && params.e2etest) { + return true; + } + + const videoParams = { + ...videoPlacement, + ...videoBidderParams // Bidder Specific overrides + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + logError('Validation failed: mimes are invalid'); + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + logError('Validation failed: protocols are invalid'); + return false; + } + + if (!videoParams.context) { + logError('Validation failed: context id not declared'); + return false; + } + + if (videoParams.context !== 'instream') { + logError('Validation failed: only context instream is supported '); + return false; + } + + if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { + logError('Validation failed: player size not declared or is not in format [[w,h]]'); + return false; + } + + return true; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildVideoRequestData(bidRequest, bidderRequest) { + const {params} = bidRequest; + + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (bidRequest.params && bidRequest.params.e2etest) { + videoParams.playerSize = [[640, 480]] + videoParams.conext = 'instream' + } + + const video = { + w: parseInt(videoParams.playerSize[0][0], 10), + h: parseInt(videoParams.playerSize[0][1], 10), + } + + // Obtain all ORTB params related video from Ad Unit + OPENRTB_VIDEO_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + // Placement Inference Rules: + // - If no placement is defined then default to 1 (In Stream) + video.placement = video.placement || 2; + + // - If product is instream (for instream context) then override placement to 1 + if (params.context === 'instream') { + video.startdelay = video.startdelay || 0; + video.placement = 1; + } + + // bid floor + const bidFloorRequest = { + currency: bidRequest.params.cur || 'USD', + mediaType: 'video', + size: '*' + }; + let floorData = bidRequest.params + if (isFn(bidRequest.getFloor)) { + floorData = bidRequest.getFloor(bidFloorRequest); + } else { + if (params.bidfloor) { + floorData = {floor: params.bidfloor, currency: params.currency || 'USD'}; + } + } + + const openrtbRequest = { + id: bidRequest.bidId, + imp: [ + { + id: '1', + video: video, + secure: isSecure() ? 1 : 0, + bidfloor: floorData.floor, + bidfloorcur: floorData.currency + } + ], + site: { + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, + }, + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: spec.VERSION, + }, + }; + + // content + if (videoParams.content && isPlainObject(videoParams.content)) { + openrtbRequest.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language', 'url']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + const contentObjectKeys = ['ext']; + for (const contentKey in videoBidderParams.content) { + if ( + (contentStringKeys.indexOf(contentKey) > -1 && isStr(videoParams.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(videoParams.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(videoParams.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && isArray(videoParams.content[contentKey]) && + videoParams.content[contentKey].every(catStr => isStr(catStr)))) { + openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; + } else { + logMessage('KultureMedia bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); + } + } + } + + return openrtbRequest; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildBannerRequestData(bidRequests, bidderRequest) { + const impr = bidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { + format: bidRequest.mediaTypes.banner.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + ext: { + exchange: { + placementId: bidRequest.params.placementId + } + } + })); + + const openrtbRequest = { + id: bidderRequest.auctionId, + imp: impr, + site: { + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, + }, + ext: {} + }; + return openrtbRequest; +} + +function _createBidResponse(bid) { + const isADomainPresent = + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, + bidderCode: spec.code, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + mediaType: deepAccess(bid, 'ext.prebid.type', BANNER) + } + + if (isADomainPresent) { + bidResponse.meta = { + advertiserDomains: bid.adomain + }; + } + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = bid.adm; + } + + return bidResponse; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/kulturemediaBidAdapter.md b/modules/kulturemediaBidAdapter.md new file mode 100644 index 00000000000..0bd17e97982 --- /dev/null +++ b/modules/kulturemediaBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name: Kulture Media Bid Adapter +Module Type: Bidder Adapter +Maintainer: devops@kulture.media +``` + +# Description + +Module that connects to Kulture Media's demand sources. +Kulture Media bid adapter supports Banner and Video. + + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + placementId: 'test', + publisherId: 'test', + } + }] + } +]; +``` + +## Video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video` +- 'mimes', +- 'minduration', +- 'maxduration', +- 'placement', +- 'protocols', +- 'startdelay', +- 'skip', +- 'skipafter', +- 'minbitrate', +- 'maxbitrate', +- 'delivery', +- 'playbackmethod', +- 'api', +- 'linearity' + + +## Instream Video adUnit using mediaTypes.video +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + bidfloor: 0.5, + publisherId: '12345', + placementId: '6789' + } + } + ] + } + ] +``` + +# End To End testing mode +By passing bid.params.e2etest = true you will be able to receive a test creative + +## Banner +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + e2etest: true + } + }] + } +]; +``` + +## Video +``` +var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + e2etest: true + } + } + ] + } +] +``` diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js new file mode 100644 index 00000000000..9fa3081a47e --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.js @@ -0,0 +1,570 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +var BIDDER_CODE = 'lemmadigital'; +var LOG_WARN_PREFIX = 'LEMMADIGITAL: '; +var ENDPOINT = 'https://bid.lemmadigital.com/lemma/servad'; +var USER_SYNC = 'https://sync.lemmadigital.com/js/usersync.html?'; +var DEFAULT_CURRENCY = 'USD'; +var AUCTION_TYPE = 2; +var DEFAULT_TMAX = 300; +var DEFAULT_NET_REVENUE = false; +var DEFAULT_SECURE = 1; +var RESPONSE_TTL = 300; +var pubId = 0; +var adunitId = 0; + +export var spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + **/ + isBidRequestValid: (bid) => { + if (!bid || !bid.params) { + utils.logError(LOG_WARN_PREFIX, 'nil/empty bid object'); + return false; + } + if (!utils.isEmpty(bid.params.pubId) || !utils.isNumber(bid.params.pubId)) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be string. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + if (!bid.params.adunitId) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: adUnitId is mandatory. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + // video bid request validation + if (bid.params.hasOwnProperty('video')) { + if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + return false; + } + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + **/ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0) { + return; + } + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = spec._setRefURL(refererInfo); + const request = spec._createoRTBRequest(validBidRequests, conf); + if (request && request.imp.length == 0) { + return; + } + spec._setOtherParams(bidderRequest, request); + const endPoint = spec._endPointURL(validBidRequests); + return { + method: 'POST', + url: endPoint, + data: JSON.stringify(request), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + **/ + interpretResponse: (response, request) => { + return spec._parseRTBResponse(request, response.body); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + **/ + getUserSyncs: (syncOptions, serverResponses) => { + let syncurl = USER_SYNC + 'pid=' + pubId; + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncurl + }]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + } + }, + + /** + * Generate UUID + */ + _createUUID: () => { + return new Date().getTime().toString(); + }, + + /** + * parse object + **/ + _parseJSON: function (rawPayload) { + try { + if (rawPayload) { + return JSON.parse(rawPayload); + } + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'Exception: ', ex); + } + return null; + }, + + /** + * + * set referal url + */ + _setRefURL: (refererInfo) => { + var conf = {}; + conf.pageURL = (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href; + if (refererInfo && refererInfo.referer) { + conf.refURL = refererInfo.referer; + } else { + conf.refURL = ''; + } + return conf; + }, + + /** + * set other params into oRTB request + */ + _setOtherParams: (request, ortbRequest) => { + var params = request && request.params ? request.params : null; + if (params) { + ortbRequest.tmax = params.tmax; + ortbRequest.bcat = params.bcat; + } + }, + + /** + * create IAB standard OpenRTB bid request + **/ + _createoRTBRequest: (bidRequests, conf) => { + var oRTBObject = {}; + try { + oRTBObject = { + id: spec._createUUID(), + at: AUCTION_TYPE, + tmax: DEFAULT_TMAX, + cur: [DEFAULT_CURRENCY], + imp: spec._getImpressionArray(bidRequests), + user: {}, + ext: {} + }; + var bid = bidRequests[0]; + + var site = spec._getSiteObject(bid, conf); + if (site) { + oRTBObject.site = site; + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + oRTBObject.site.content = config.getConfig('content'); + } + } + var app = spec._getAppObject(bid); + if (app) { + oRTBObject.app = app; + if (typeof oRTBObject.app.content !== 'object' && typeof config.getConfig('content') === 'object') { + oRTBObject.app.content = + config.getConfig('content') || undefined; + } + } + var device = spec._getDeviceObject(bid); + if (device) { + oRTBObject.device = device; + } + var source = spec._getSourceObject(bid); + if (source) { + oRTBObject.source = source; + } + return oRTBObject; + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', ex); + } + }, + + /** + * create impression array objects + **/ + _getImpressionArray: (request) => { + var impArray = []; + var map = request.map(bid => spec._getImpressionObject(bid)); + if (map) { + map.forEach(o => { + if (o) { + impArray.push(o); + } + }); + } + return impArray; + }, + + /** + * create impression (single) object + **/ + _getImpressionObject: (bid) => { + var impression = {}; + var bObj; + var vObj; + var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; + var mediaTypes = ''; + var format = []; + var params = bid && bid.params ? bid.params : null; + impression = { + id: bid.bidId, + tagid: params.adunitId ? params.adunitId.toString() : undefined, + secure: DEFAULT_SECURE, + bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY + }; + if (params.bidFloor) { + impression.bidfloor = params.bidFloor; + } + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bObj = spec._getBannerRequest(bid); + if (bObj) { + impression.banner = bObj; + } + break; + case VIDEO: + vObj = spec._getVideoRequest(bid); + if (vObj) { + impression.video = vObj; + } + break; + } + } + } else { + bObj = { + pos: 0, + w: sizes && sizes[0] ? sizes[0][0] : 0, + h: sizes && sizes[0] ? sizes[0][1] : 0, + }; + if (utils.isArray(sizes) && sizes.length > 1) { + sizes = sizes.splice(1, sizes.length - 1); + sizes.forEach(size => { + format.push({ + w: size[0], + h: size[1] + }); + }); + bObj.format = format; + } + impression.banner = bObj; + } + spec._setFloor(impression, bid); + return impression.hasOwnProperty(BANNER) || + impression.hasOwnProperty(VIDEO) ? impression : undefined; + }, + + /** + * set bid floor + **/ + _setFloor: (impObj, bid) => { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function') { + [BANNER, VIDEO].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)); + } + } + }); + } + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor); + } + + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined); + }, + + /** + * parse Open RTB response + **/ + _parseRTBResponse: (request, response) => { + var bidResponses = []; + try { + if (response.seatbid) { + var currency = response.curr || DEFAULT_CURRENCY; + var seatbid = response.seatbid; + seatbid.forEach(seatbidder => { + var bidder = seatbidder.bid; + bidder.forEach(bid => { + var req = spec._parseJSON(request.data); + var newBid = { + requestId: bid.impid, + cpm: parseFloat(bid.price).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: currency, + netRevenue: DEFAULT_NET_REVENUE, + ttl: RESPONSE_TTL, + referrer: req.site.ref, + ad: bid.adm + }; + if (bid.dealid) { + newBid.dealId = bid.dealid; + } + if (req.imp && req.imp.length > 0) { + req.imp.forEach(robj => { + if (bid.impid === robj.id) { + spec._checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case VIDEO: + newBid.width = bid.hasOwnProperty('w') ? bid.w : robj.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : robj.video.h; + newBid.vastXml = bid.adm; + break; + } + } + }); + } + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', error); + } + return bidResponses; + }, + + /** + * get bid request api end point url + **/ + _endPointURL: (request) => { + var params = request && request[0].params ? request[0].params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + adunitId = params.adunitId ? params.adunitId : 0; + return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; + } + return null; + }, + + /** + * get domain name from url + **/ + _getDomain: (url) => { + var a = document.createElement('a'); + a.setAttribute('href', url); + return a.hostname; + }, + + /** + * create the site object + **/ + _getSiteObject: (request, conf) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : '0'; + var siteId = params.siteId ? params.siteId : '0'; + var appParams = params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString() + }, + domain: spec._getDomain(conf.pageURL), + id: siteId.toString(), + ref: conf.refURL, + page: conf.pageURL, + cat: params.category, + pagecat: params.page_category + }; + } + } + return null; + }, + + /** + * create the app object + **/ + _getAppObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + var appParams = params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + id: appParams.id, + name: appParams.name, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + cat: appParams.cat || params.category, + pagecat: appParams.pagecat || params.page_category + }; + } + } + return null; + }, + + /** + * create the device object + **/ + _getDeviceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + dnt: utils.getDNT() ? 1 : 0, + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + geo: { + country: params.country, + lat: params.latitude, + lon: params.longitude, + accuracy: params.accuracy, + region: params.region, + city: params.city, + zip: params.zip + }, + ip: params.ip, + make: params.make, + model: params.model, + os: params.os, + carrier: params.carrier, + devicetype: params.device_type, + ifa: params.ifa, + }; + } + return null; + }, + + /** + * create source object + */ + _getSourceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + pchain: params.pchain, + ext: { + schain: request.schain + }, + }; + } + return null; + }, + + /** + * get request ad sizes + **/ + _getSizes: (request) => { + if (request && request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) { + return request.sizes[0]; + } + return null; + }, + + /** + * create the banner object + **/ + _getBannerRequest: (bid) => { + var bObj; + var adFormat = []; + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + var params = bid ? bid.params : null; + var bannerData = params && params.banner; + var sizes = spec._getSizes(bid) || []; + if (sizes && sizes.length == 0) { + sizes = bid.mediaTypes.banner.sizes[0]; + } + if (sizes && sizes.length > 0) { + bObj = {}; + bObj.w = sizes[0]; + bObj.h = sizes[1]; + bObj.pos = 0; + if (bannerData) { + bObj = utils.deepClone(bannerData); + } + sizes = bid.mediaTypes.banner.sizes; + if (sizes.length > 0) { + adFormat = []; + sizes.forEach(function (size) { + if (size.length > 1) { + adFormat.push({ w: size[0], h: size[1] }); + } + }); + if (adFormat.length > 0) { + bObj.format = adFormat; + } + } + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return bObj; + }, + + /** + * create the video object + **/ + _getVideoRequest: (bid) => { + var vObj; + if (utils.deepAccess(bid, 'mediaTypes.video')) { + var params = bid ? bid.params : null; + var videoData = utils.mergeDeep(utils.deepAccess(bid.mediaTypes, 'video'), params.video); + var sizes = bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : [] + if (sizes && sizes.length > 0) { + vObj = {}; + if (videoData) { + vObj = utils.deepClone(videoData); + } + vObj.w = sizes[0]; + vObj.h = sizes[1]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.video.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return vObj; + }, + + /** + * check media type + **/ + _checkMediaType: (adm, newBid) => { + // Create a regex here to check the strings + var videoRegex = new RegExp(/VAST.*version/); + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; + } + } +}; + +registerBidder(spec); diff --git a/modules/lemmaDigitalBidAdapter.md b/modules/lemmaDigitalBidAdapter.md new file mode 100644 index 00000000000..5a22a7588da --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Lemmadigital Bid Adapter +Module Type: Bidder Adapter +Maintainer: lemmadev@lemmatechnologies.com +``` + +# Description + +Connects to Lemma exchange for bids. +Lemmadigital bid adapter supports Video, Banner formats. + +# Sample Banner Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'div-lemma-ad-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], // required + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3768', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 2 + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +``` +var adUnits = [{ + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream' + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3769', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 4, + video: { + mimes: ['video/mp4','video/x-flv'], // required + } + } + }] +}]; +``` diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 9f45daeea29..4fd2a80afd0 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -11,6 +11,7 @@ import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/val import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); const defaultRequestedAttributes = {'nonId': true} @@ -39,14 +40,22 @@ let liveConnect = null; * This function is used in tests */ export function reset() { - if (window && window.liQ) { - window.liQ = []; + if (window && window.liQ_instances) { + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances = []; } liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } +/** + * This function is also used in tests + */ +export function setEventFiredFlag() { + eventFired = true; +} + function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; collectConfig = collectConfig || {} @@ -100,6 +109,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + liveConnectConfig.fireEventDelay = configParams.fireEventDelay; const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; @@ -121,8 +131,14 @@ function initializeLiveConnect(configParams) { function tryFireEvent() { if (!eventFired && liveConnect) { - liveConnect.fire(); - eventFired = true; + const eventDelay = liveConnect.config.fireEventDelay || 500 + setTimeout(() => { + const instances = window.liQ_instances + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + if (!eventFired && liveConnect) { + liveConnect.fire(); + } + }, eventDelay) } } diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 12da791d2c4..632403c6643 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,5 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + const BIDDER_CODE = 'mabidder'; export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; export const spec = { @@ -33,7 +35,7 @@ export const spec = { url: baseUrl, method: 'POST', data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, bids: bids, url: bidderRequest.refererInfo.page || '', referer: bidderRequest.refererInfo.ref || '', diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js index c86cc4dfbc2..f1e53a3c20c 100644 --- a/modules/marsmediaAnalyticsAdapter.js +++ b/modules/marsmediaAnalyticsAdapter.js @@ -1,6 +1,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; /**** * Mars Media Analytics @@ -33,7 +34,7 @@ var marsmediaAnalyticsAdapter = Object.assign(adapter( success: function() {}, error: function() {} }, - JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': $$PREBID_GLOBAL$$.getBidResponses(), ver: MARS_VERSION}), + JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': getGlobal().getBidResponses(), ver: MARS_VERSION}), { method: 'POST' } diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 9b1b02e00a2..d807c9b801e 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -19,38 +19,6 @@ const storage = getStorageManager(); let globals = {}; let itemMaps = {}; -/** - * 获取随机id - * @param {number} a random number from 0 to 15 - * @return {string} random number or random string - */ -// function getRandomId( -// a // placeholder -// ) { -// // if the placeholder was passed, return -// // a random number from 0 to 15 -// return a -// ? ( -// a ^ // unless b is 8, -// ((Math.random() * // in which case -// 16) >> // a random number from -// (a / 4)) -// ) // 8 to 11 -// .toString(16) // in hexadecimal -// : ( // or otherwise a concatenated string: -// [1e7] + // 10000000 + -// 1e3 + // -1000 + -// 4e3 + // -4000 + -// 8e3 + // -80000000 + -// 1e11 -// ) // -100000000000, -// .replace( -// // replacing -// /[018]/g, // zeroes, ones, and eights with -// getRandomId // random hex digits -// ); -// } - /* ----- mguid:start ------ */ const COOKIE_KEY_MGUID = '__mguid_'; @@ -247,7 +215,9 @@ function getItems(validBidRequests, bidderRequest) { } } if (!matchSize) { - return {}; + matchSize = sizes[0] + ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } + : { h: 0, w: 0 }; } const bidFloor = getBidFloor(req); @@ -267,6 +237,7 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: { // gpid: gpid, // 加入后无法返回广告 @@ -310,6 +281,7 @@ function getParam(validBidRequests, bidderRequest) { const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const timeout = bidderRequest.timeout || 2000; + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { @@ -330,6 +302,7 @@ function getParam(validBidRequests, bidderRequest) { }, ext: { eids, + firstPartyData, }, user: { buyeruid: getUserID(), diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index d2d3d2bf888..b902727a730 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -8,8 +8,7 @@ import { logError, logInfo, triggerPixel, - uniques, - getHighestCpm + uniques } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -18,6 +17,7 @@ import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; import {includes} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -37,10 +37,11 @@ const PRICE_GRANULARITY = { const MEDIANET_BIDDER_CODE = 'medianet'; // eslint-disable-next-line no-undef -const PREBID_VERSION = $$PREBID_GLOBAL$$.version; +const PREBID_VERSION = getGlobal().version; const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; +const ERROR_WINNING_AUCTION_MISSING = 'winning_auction_missing'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; @@ -72,7 +73,7 @@ class ErrorLogger { this.evtid = 'projectevents'; this.project = 'prebidanalytics'; this.dn = pageDetails.domain || ''; - this.requrl = pageDetails.requrl || ''; + this.requrl = pageDetails.topmostLocation || ''; this.pbversion = PREBID_VERSION; this.cid = config.cid || ''; this.rd = additionalData; @@ -270,6 +271,52 @@ class AdSlot { } } +class BidWrapper { + constructor() { + this.bidReqs = []; + this.bidObjs = []; + } + + findReqBid(bidId) { + return this.bidReqs.find(bid => { + return bid['bidId'] === bidId + }); + } + + findBidObj(key, value) { + return this.bidObjs.find(bid => { + return bid[key] === value + }); + } + + addBidReq(bidRequest) { + this.bidReqs.push(bidRequest) + } + + addBidObj(bidObj) { + if (!(bidObj instanceof Bid)) { + bidObj = Bid.getInstance(bidObj); + } + const bidReq = this.findReqBid(bidObj.bidId); + if (bidReq instanceof Bid) { + bidReq.used = true; + } + this.bidObjs.push(bidObj); + } + + getAdSlotBids(adSlot) { + const bidResponses = this.getAdSlotBidObjs(adSlot); + return bidResponses.map((bid) => bid.getLoggingData()); + } + + getAdSlotBidObjs(adSlot) { + const bidResponses = this.bidObjs + .filter((bid) => bid.adUnitCode === adSlot); + const remResponses = this.bidReqs.filter(bid => !bid.used && bid.adUnitCode === adSlot); + return [...bidResponses, ...remResponses]; + } +} + class Bid { constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) { this.bidId = bidId; @@ -299,6 +346,9 @@ class Bid { this.floorPrice = undefined; this.floorRule = undefined; this.serverLatencyMillis = undefined; + this.used = false; + this.originalRequestId = bidId; + this.requestId = undefined; } get size() { @@ -308,8 +358,15 @@ class Bid { return this.width + 'x' + this.height; } + static getInstance(bidProps) { + const bidObj = new Bid(); + return bidProps && Object.assign(bidObj, bidProps); + } + getLoggingData() { return { + reqId: this.requestId || this.bidId, + ogReqId: this.originalRequestId, adid: this.adId, pvnm: this.bidder, src: this.src, @@ -341,7 +398,7 @@ class Auction { constructor(acid) { this.acid = acid; this.status = AUCTION_IN_PROGRESS; - this.bids = []; + this.bidWrapper = new BidWrapper(); this.adSlots = {}; this.auctionInitTime = undefined; this.auctionStartTime = undefined; @@ -378,24 +435,31 @@ class Auction { addSlot({ adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, tmax, adext, context }) { if (adUnitCode && this.adSlots[adUnitCode] === undefined) { this.adSlots[adUnitCode] = new AdSlot(tmax, supplyAdCode, context, adext); - this.addBid(new Bid('-1', DUMMY_BIDDER, 'client', '-1', adUnitCode, mediaTypes, allMediaTypeSizes)); + this.addBidObj(new Bid('-1', DUMMY_BIDDER, 'client', Date.now(), adUnitCode, mediaTypes, allMediaTypeSizes)); } } addBid(bid) { - this.bids.push(bid); + this.bidWrapper.addBidReq(bid); } - findBid(key, value) { - return this.bids.filter(bid => { - return bid[key] === value - })[0]; + addBidObj(bidObj) { + this.bidWrapper.addBidObj(bidObj) } - getAdslotBids(adslot) { - return this.bids - .filter((bid) => bid.adUnitCode === adslot) - .map((bid) => bid.getLoggingData()); + findReqBid(bidId) { + return this.bidWrapper.findReqBid(bidId) + } + + findBidObj(key, value) { + return this.bidWrapper.findBidObj(key, value) + } + + getAdSlotBids(adSlot) { + return this.bidWrapper.getAdSlotBids(adSlot); + } + getAdSlotBidObjs(adSlot) { + return this.bidWrapper.getAdSlotBidObjs(adSlot); } _mergeFieldsToLog(objParams) { @@ -494,41 +558,26 @@ function _getSizes(mediaTypes, sizes) { } } -/* - - The code is used to determine if the current bid is higher than the previous bid. - - If it is, then the code will return true and if not, it will return false. - */ -function canSelectCurrentBid(previousBid, currentBid) { - if (!(previousBid instanceof Bid)) return false; - - // For first bid response the previous bid will be containing bid request obj - // in which the cpm would be undefined so the current bid can directly be selected. - const isFirstBidResponse = previousBid.cpm === undefined && currentBid.cpm !== undefined; - if (isFirstBidResponse) return true; - - // if there are 2 bids, get the highest bid - const selectedBid = getHighestCpm(previousBid, currentBid); - - // Return true if selectedBid is currentBid, - // The timeToRespond field is used as an identifier for distinguishing - // between the current iterating bid and the previous bid. - return selectedBid.timeToRespond === currentBid.timeToRespond; -} - function bidResponseHandler(bid) { - const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; - const {originalCpm, bidderCode, creativeId, adId, currency} = bid; + const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder } = bid; + const {originalCpm, creativeId, adId, currency} = bid; if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', requestId); - if (!canSelectCurrentBid(bidObj, bid)) { - return; + const reqId = originalRequestId || requestId; + const bidReq = auctions[auctionId].findReqBid(reqId); + + if (!(bidReq instanceof Bid)) return; + + let bidObj = auctions[auctionId].findBidObj('bidId', requestId); + let isBidOverridden = true; + if (!bidObj || bidObj.status === BID_SUCCESS) { + bidObj = {}; + isBidOverridden = false; } - Object.assign( - bidObj, - { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, + Object.assign(bidObj, bidReq, + { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId }, { adId, currency } ); bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); @@ -547,7 +596,7 @@ function bidResponseHandler(bid) { bidObj.status = BID_SUCCESS; } - if (bidderCode === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { + if (bidder === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { Object.assign( bidObj, { 'ext': bid.ext }, @@ -558,6 +607,7 @@ function bidResponseHandler(bid) { if (typeof bid.serverResponseTimeMs !== 'undefined') { bidObj.serverLatencyMillis = bid.serverResponseTimeMs; } + !isBidOverridden && auctions[auctionId].addBidObj(bidObj); } function noBidResponseHandler({ auctionId, bidId }) { @@ -567,11 +617,13 @@ function noBidResponseHandler({ auctionId, bidId }) { if (auctions[auctionId].hasEnded()) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid(bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_NOBID; + auctions[auctionId].addBidObj(bidObj); } function bidTimeoutHandler(timedOutBids) { @@ -579,11 +631,13 @@ function bidTimeoutHandler(timedOutBids) { if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid('bidId', bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_TIMEOUT; + auctions[auctionId].addBidObj(bidObj); }) } @@ -614,13 +668,13 @@ function setTargetingHandler(params) { const winnerAdId = params[adunit][CONSTANTS.TARGETING_KEYS.AD_ID]; let winningBid; let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); - auctionObj.bids.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { + auctionObj.bidWrapper.bidObjs.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { bid.iwb = 1; if (bid.adId === winnerAdId) { winningBid = bid; } }); - auctionObj.bids.forEach(bid => { + auctionObj.bidWrapper.bidObjs.forEach(bid => { if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { bid.iwb = bidAdIds.length === 0 ? 0 : 1; bid.width = deepAccess(winningBid, 'width'); @@ -633,16 +687,27 @@ function setTargetingHandler(params) { } function bidWonHandler(bid) { - const { auctionId, adUnitCode, adId } = bid; + const { auctionId, adUnitCode, adId, bidder, requestId, originalRequestId } = bid; if (!(auctions[auctionId] instanceof Auction)) { + new ErrorLogger(ERROR_WINNING_AUCTION_MISSING, { + adId, + auctionId, + adUnitCode, + bidder, + requestId, + originalRequestId + }).send(); return; } - let bidObj = auctions[auctionId].findBid('adId', adId); + let bidObj = auctions[auctionId].findBidObj('adId', adId); if (!(bidObj instanceof Bid)) { new ErrorLogger(ERROR_WINNING_BID_ABSENT, { - adId: adId, - acid: auctionId, + adId, + auctionId, adUnitCode, + bidder, + requestId, + originalRequestId }).send(); return; } @@ -696,13 +761,13 @@ function fireAuctionLog(acid, adtag, logType, adId) { let bidParams; if (logType === LOG_TYPE.RA) { - const winningBidObj = auctions[acid].findBid('adId', adId); + const winningBidObj = auctions[acid].findBidObj('adId', adId); if (!winningBidObj) return; const winLogData = winningBidObj.getLoggingData(); bidParams = [winLogData]; commonParams.lper = 1; } else { - bidParams = auctions[acid].getAdslotBids(adtag).map(({winner, ...restParams}) => restParams); + bidParams = auctions[acid].getAdSlotBids(adtag).map(({winner, ...restParams}) => restParams); delete commonParams.wts; } let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0; @@ -822,8 +887,8 @@ medianetAnalytics.enableAnalytics = function (configuration) { logError('Media.net Analytics adapter: cid is required.'); return; } - $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled = true; + getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; + getGlobal().medianetGlobals.analyticsEnabled = true; pageDetails = new PageDetail(); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 01652a3fac0..5f54b2e3ff3 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -17,6 +17,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; @@ -51,7 +52,7 @@ const aliases = [ { code: 'aax', gvlid: 720 }, ]; -$$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; +getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; function getTopWindowReferrer() { try { @@ -177,7 +178,7 @@ function extParams(bidRequest, bidderRequests) { const coppaApplies = !!(config.getConfig('coppa')); return Object.assign({}, { customer_id: params.cid }, - { prebid_version: $$PREBID_GLOBAL$$.version }, + { prebid_version: getGlobal().version }, { gdpr_applies: gdprApplies }, (gdprApplies) && { gdpr_consent_string: gdpr.consentString || '' }, { usp_applies: uspApplies }, @@ -185,7 +186,7 @@ function extParams(bidRequest, bidderRequests) { {coppa_applies: coppaApplies}, windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }, + getGlobal().medianetGlobals.analyticsEnabled && { analytics: true }, !isEmpty(sChain) && {schain: sChain} ); } @@ -358,7 +359,7 @@ function getLoggingData(event, data) { params.evtid = 'projectevents'; params.project = 'prebid'; params.acid = deepAccess(data, '0.auctionId') || ''; - params.cid = $$PREBID_GLOBAL$$.medianetGlobals.cid || ''; + params.cid = getGlobal().medianetGlobals.cid || ''; params.crid = data.map((adunit) => deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); params.adunit_count = data.length || 0; params.dn = mnData.urlData.domain || ''; @@ -442,7 +443,7 @@ export const spec = { return false; } - Object.assign($$PREBID_GLOBAL$$.medianetGlobals, !$$PREBID_GLOBAL$$.medianetGlobals.cid && {cid: bid.params.cid}); + Object.assign(getGlobal().medianetGlobals, !getGlobal().medianetGlobals.cid && {cid: bid.params.cid}); return true; }, diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index 578db846289..69b2387d053 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -1,7 +1,8 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; const GVLID = 918; const DEFAULT_SUB_DOMAIN = 'exchange'; @@ -22,11 +23,11 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -54,9 +55,22 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - let { bidFloor, ext } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; const hashUrl = hashCode(topWindowUrl); const uniqueDealId = getUniqueDealId(hashUrl); const cId = extractCID(params); @@ -90,11 +104,24 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, schain: schain, - mediaTypes: mediaTypes + mediaTypes: mediaTypes, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout }; appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; @@ -107,6 +134,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -148,10 +183,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -161,14 +197,14 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -207,8 +243,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -232,7 +268,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -256,7 +294,8 @@ export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -264,9 +303,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 02fd454c780..f461440c5a1 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -60,6 +60,7 @@ export const spec = { if (bidRequest.params.isInternal) { payload.is_internal = bidRequest.params.isInternal; } + payload.userEids = bidRequest.userIdAsEids || []; return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -127,7 +128,7 @@ export const spec = { protocol: 'https', hostname, pathname: '/v1/bidsuccess', - search: { t: bid.params[0].apiKey }, + search: { t: bid.params[0].apiKey, provider: bid.meta?.networkName, cpm: bid.cpm, currency: bid.currency }, }) ); logInfo('Missena - Bid won', bid); diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index e13c6151e5d..2ee4601e200 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -11,7 +11,7 @@ const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' -function init(config = {}, userConsent = '') { +function init(config = {}, userConsent) { config.params = config.params || {} // ignore module if publicToken is missing (module setup failure) if (!config.params.publicToken) { @@ -30,9 +30,10 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen logInfo('NeuwoRTDModule', 'starting getBidRequestData') const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); - const url = config.params.apiUrl + [ + /* adjust for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') */ + const joiner = config.params.apiUrl.indexOf('?') < 0 ? '?' : '&' + const url = config.params.apiUrl + joiner + [ 'token=' + config.params.publicToken, - 'lang=en', 'url=' + wrappedArgUrl ].join('&') const billingId = generateUUID(); diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 6abb369522b..6f9385094e7 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -88,6 +88,7 @@ export const spec = { }; const imp = { + id: bid.adUnitCode, ext: { prebid: { storedrequest: {id} diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 397196ee7a9..a1971f4f9a5 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,252 +1,177 @@ import {config} from '../src/config.js'; -import * as utils from '../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js'; +import {getStorageManager} from '../src/storageManager.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import { INSTREAM, OUTSTREAM } from '../src/video.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', - 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', - 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; - -const PAGE_VIEW_ID = utils.generateUUID(); - -const BIDDER_VERSION = '1.0'; - +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '2.0'; const GVLID = 965; +const NEXXID_KEY = 'nexx360_storage'; -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: [ - { code: 'revenuemaker' }, - { code: 'first-id', gvlid: 1178 }, - { code: 'adwebone' }, - { code: 'league-m', gvlid: 965 } - ], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, -}; +const ALIASES = [ + { code: 'revenuemaker' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, + { code: 'league-m', gvlid: 965 } +]; -registerBidder(spec); - -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { - return !!bid.params.tagId || !!bid.params.videoTagId; -}; +export const storage = getStorageManager({ + gvlid: GVLID, + bidderCode: BIDDER_CODE, +}); /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Get the NexxId + * @param + * @return {object | false } false if localstorageNotEnabled + */ -function buildRequests(bids, bidderRequest) { - const data = getBaseRequest(bids[0], bidderRequest); - bids.forEach((bid) => { - const impObject = createImpObject(bid); - if (isBannerBid(bid)) impObject.banner = getBannerObject(bid); - if (isVideoBid(bid)) impObject.video = getVideoObject(bid); - data.imp.push(impObject); - }); - return { - method: 'POST', - url: REQUEST_URL, - data, +export function getNexx360LocalStorage() { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for Nexx360`); + return false; } -} - -function createImpObject(bid) { - const floor = getFloor(bid, BANNER); - const imp = { - id: bid.bidId, - tagid: bid.adUnitCode, - ext: { - divId: bid.adUnitCode, - nexx360: { - videoTagId: bid.params.videoTagId, - tagId: bid.params.tagId, - allBids: bid.params.allBids === true, - } - } - }; - enrichImp(imp, bid, floor); - return imp; -} - -function getBannerObject(bid) { - return { - format: toFormat(bid.mediaTypes.banner.sizes), - topframe: utils.inIframe() ? 0 : 1 - }; -} - -function getVideoObject(bid) { - let width, height; - const videoParams = utils.deepAccess(bid, `mediaTypes.video`); - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - // normalize config for video size - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (utils.isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); + const output = storage.getDataFromLocalStorage(NEXXID_KEY); + if (output === null) { + const nexx360Storage = { nexx360Id: generateUUID() }; + storage.setDataInLocalStorage(NEXXID_KEY, JSON.stringify(nexx360Storage)); + return nexx360Storage; } - const video = { - playerSize: [height, width], - context, - }; - - Object.keys(videoParams) - .filter(param => VIDEO_TARGETING.includes(param)) - .forEach(param => video[param] = videoParams[param]); - return video; -} - -function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); -} - -function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function toFormat(sizes) { - return sizes.map((s) => { - return { w: s[0], h: s[1] }; - }); -} - -function getFloor(bid, mediaType) { - let floor = 0; - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: mediaType, - size: '*' - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } + try { + return JSON.parse(output) + } catch (e) { + return false; } - - return floor; } -function enrichImp(imp, bid, floor) { - if (floor > 0) { - imp.bidfloor = floor; - imp.bidfloorcur = 'USD'; - } else if (bid.params.customFloor) { - imp.bidfloor = bid.params.customFloor; - } - if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { - imp.ext.data = bid.ortb2Imp.ext.data; +function getAdContainer(container) { + if (document.getElementById(container)) { + return document.getElementById(container); } } -function getBaseRequest(bid, bidderRequest) { - let req = { - id: bidderRequest.auctionId, - imp: [], - cur: [config.getConfig('currency.adServerCurrency') || 'USD'], - at: 1, - tmax: config.getConfig('bidderTimeout'), - site: { - page: bidderRequest.refererInfo.topmostLocation || bidderRequest.refererInfo.page, - domain: bidderRequest.refererInfo.domain, - }, - regs: { - coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, - }, - device: { - dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, - h: screen.height, - w: screen.width, - ua: window.navigator.userAgent, - language: window.navigator.language.split('-').shift() - }, - user: {}, - ext: { - source: 'prebid.js', - version: '$prebid.version$', - pageViewId: PAGE_VIEW_ID, - bidderVersion: BIDDER_VERSION, - } - }; - - if (bid.params.platform) { - utils.deepSetValue(req, 'ext.platform', bid.params.platform); - } - if (bid.params.response_template_name) { - utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); - } - req.test = config.getConfig('debug') ? 1 : 0; - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); - } - if (bidderRequest.gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest, context) { + // console.log(bidRequest, context); + const imp = buildImp(bidRequest, context); + const tagid = bidRequest.params.adUnitName ? bidRequest.params.adUnitName : bidRequest.adUnitCode; + deepSetValue(imp, 'tagid', tagid); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + const divId = bidRequest.params.divId ? bidRequest.params.divId : bidRequest.adUnitCode; + deepSetValue(imp, 'ext.divId', divId); + const slotEl = getAdContainer(divId); + if (slotEl) { + deepSetValue(imp, 'ext.dimensions.slotW', slotEl.offsetWidth); + deepSetValue(imp, 'ext.dimensions.slotH', slotEl.offsetHeight); + deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); + deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); } - if (bidderRequest.gdprConsent.addtlConsent !== undefined) { - utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + deepSetValue(imp, 'ext.nexx360', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); + deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); } + + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const nexx360LocalStorage = getNexx360LocalStorage(); + if (nexx360LocalStorage) deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + deepSetValue(request, 'ext.version', '$prebid.version$'); + deepSetValue(request, 'ext.source', 'prebid.js'); + deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); + deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); + deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); + if (!request.user) deepSetValue(request, 'user', {}); + return request; + }, +}); + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(bid) { + if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { + logError('bid.params.adUnitName needs to be a string'); + return false; } - if (bidderRequest.uspConsent) { - utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + if (bid.params.adUnitPath && (typeof bid.params.adUnitPath !== 'string' || bid.params.adUnitPath === '')) { + logError('bid.params.adUnitPath needs to be a string'); + return false; } - if (bid.schain) { - utils.deepSetValue(req, 'source.ext.schain', bid.schain); + if (bid.params.divId && (typeof bid.params.divId !== 'string' || bid.params.divId === '')) { + logError('bid.params.divId needs to be a string'); + return false; } - if (bid.userIdAsEids) { - utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + if (bid.params.allBids && typeof bid.params.allBids !== 'boolean') { + logError('bid.params.allBids needs to be a boolean'); + return false; } - const commonFpd = bidderRequest.ortb2 || {}; - if (commonFpd.site) { - utils.mergeDeep(req, {site: commonFpd.site}); + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId must be defined'); + return false; } - if (commonFpd.user) { - utils.mergeDeep(req, {user: commonFpd.user}); + return true; +}; + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + return { + method: 'POST', + url: REQUEST_URL, + data, } - return req; } /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(response, req) { - const { bidderSettings } = getGlobal(); - const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - const respBody = response.body; + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + +function interpretResponse(serverResponse) { + const respBody = serverResponse.body; if (!respBody || !Array.isArray(respBody.seatbid)) { return []; } + const { bidderSettings } = getGlobal(); + const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; + let bids = []; respBody.seatbid.forEach(seatbid => { - const ssp = seatbid.seat; bids = [...bids, ...seatbid.bid.map(bid => { const response = { requestId: bid.impid, @@ -255,27 +180,30 @@ function interpretResponse(response, req) { height: bid.h, creativeId: bid.crid, dealId: bid.dealid, - currency: respBody.cur || 'USD', + currency: respBody.cur, netRevenue: true, ttl: 120, - mediaType: bid.type === 'banner' ? 'banner' : 'video', + mediaType: [OUTSTREAM, INSTREAM].includes(bid.ext.mediaType) ? 'video' : bid.ext.mediaType, meta: { advertiserDomains: bid.adomain, - demandSource: ssp, + demandSource: bid.ext.ssp, }, }; - if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; - - if (response.mediaType === 'banner') { - response.adUrl = bid.adUrl; - } + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ext.ssp}`; - if (['instream', 'outstream'].includes(bid.type)) response.vastXml = bid.vastXml; + if (bid.ext.mediaType === BANNER) response.adUrl = bid.ext.adUrl; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.ext.vastXml; - if (bid.ext) { - response.meta.networkId = bid.ext.dsp_id; - response.meta.advertiserId = bid.ext.buyer_id; - response.meta.brandId = bid.ext.brand_id; + if (bid.ext.mediaType === OUTSTREAM) { + response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); + response.divId = bid.ext.divId + }; + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { + ortb: JSON.parse(bid.adm) + } + } catch (e) {} } return response; })]; @@ -284,17 +212,65 @@ function interpretResponse(response, req) { } /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && - serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { - return serverResponses[0].body.cookies.slice(0, 5); + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object') { + return serverResponses[0].body.ext.cookies.slice(0, 5); } else { return []; } }; + +function outstreamRender(response) { + response.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [response.width, response.height], + targetId: response.divId, + adResponse: response.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: response.vastXml + } + }); + }); +} + +function createRenderer(bid, url) { + const renderer = Renderer.install({ + id: bid.id, + url: url, + loaded: false, + adUnitCode: bid.ext.adUnitCode, + targetId: bid.ext.divId, + }); + renderer.setRender(outstreamRender); + return renderer; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ALIASES, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 20878b545e4..6f6cbec3400 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -8,7 +8,7 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.3.2'; +window.nobidVersion = '1.3.3'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -175,6 +175,9 @@ function nobidBuildRequests(bids, bidderRequest) { if (adunitObject.div) { a.d = adunitObject.div; } + if (adunitObject.floor) { + a.floor = adunitObject.floor; + } if (adunitObject.targeting) { a.g = adunitObject.targeting; } else { @@ -201,6 +204,12 @@ function nobidBuildRequests(bids, bidderRequest) { adunits.push(a); return adunits; } + function getFloor (bid) { + if (bid && typeof bid.getFloor === 'function' && bid.getFloor().floor) { + return bid.getFloor().floor; + } + return null; + } if (typeof window.nobid.refreshLimit !== 'undefined') { if (window.nobid.refreshLimit < window.nobid.refreshCount) return false; } @@ -227,6 +236,7 @@ function nobidBuildRequests(bids, bidderRequest) { if (bid.mediaType === VIDEO || (videoMediaType && (context === 'instream' || context === 'outstream'))) { adType = 'video'; } + const floor = getFloor(bid); if (siteId) { newAdunit({ @@ -235,7 +245,8 @@ function nobidBuildRequests(bids, bidderRequest) { siteId: siteId, placementId: placementId, ad_type: adType, - params: bid.params + params: bid.params, + floor: floor }, adunits); } diff --git a/modules/nobidBidAdapter.md b/modules/nobidBidAdapter.md index 9e47aa5f43f..4449ad5c88b 100644 --- a/modules/nobidBidAdapter.md +++ b/modules/nobidBidAdapter.md @@ -4,7 +4,7 @@ title: Nobid description: Prebid Nobid Bidder Adaptor biddercode: nobid hide: true -media_types: banner +media_types: banner, video gdpr_supported: true usp_supported: true --- @@ -51,4 +51,4 @@ usp_supported: true ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index 200d2cf0fed..e550423094f 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -32,10 +32,6 @@ const converter = ortbConverter({ if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { imp.video.placement = imp.video.placement || 4; } - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { - // TODO: we may want to standardize this and move fledge logic to ortbConverter - delete imp.ext.ae; - } mergeDeep(imp, { tagid: bidRequest.params.unit, ext: { @@ -106,10 +102,12 @@ const converter = ortbConverter({ let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } }); return { bids: response.bids, diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 9979b1fdc3b..b84c67ba6d2 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storageManager = getStorageManager({bidderCode: 'orbidder'}); @@ -96,7 +97,7 @@ export const spec = { method: 'POST', options: { withCredentials: true }, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 305e175bf2c..7e53a0f5271 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,12 +8,16 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' +const logger = prefixLog('[PermutiveRTD]') + export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' +export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' +export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) @@ -24,30 +28,6 @@ function init(moduleConfig, userConsent) { return true } -/** - * Set segment targeting from cache and then try to wait for Permutive - * to initialise to get realtime segment targeting - * @param {Object} reqBidsConfigObj - * @param {function} callback - Called when submodule is done - * @param {customModuleConfig} reqBidsConfigObj - Publisher config for module - */ -export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { - const permutiveOnPage = isPermutiveOnPage() - const moduleConfig = getModuleConfig(customModuleConfig) - const segmentData = getSegments(moduleConfig.params.maxSegs) - - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - - if (moduleConfig.waitForIt && permutiveOnPage) { - window.permutive.ready(function () { - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - callback() - }, 'realtime') - } else { - callback() - } -} - function liftIntoParams(params) { return isPlainObject(params) ? { params } : {} } @@ -109,15 +89,13 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders - * @param {Object} bidderOrtb2 + * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders * @param {Object} customModuleConfig - Publisher config for module */ -export function setBidderRtb (bidderOrtb2, customModuleConfig) { - const moduleConfig = getModuleConfig(customModuleConfig) +export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] - const segmentData = getSegments(maxSegs) const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] @@ -126,28 +104,39 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { bidders.forEach(function (bidder) { const currConfig = { ortb2: bidderOrtb2[bidder] || {} } + let cohorts = [] + const isAcBidder = acBidders.indexOf(bidder) > -1 - const isSspBidder = ssps.indexOf(bidder) > -1 + if (isAcBidder) { + cohorts = segmentData.ac + } - let cohorts = [] - if (isAcBidder) cohorts = segmentData.ac - if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + const isSspBidder = ssps.indexOf(bidder) > -1 + if (isSspBidder) { + cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + } - const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) - bidderOrtb2[bidder] = nextConfig.ortb2; + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) + bidderOrtb2[bidder] = nextConfig.ortb2 }) } /** * Updates `user.data` object in existing bidder config with Permutive segments + * @param string bidder - The bidder * @param {Object} currConfig - Current bidder config * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs + * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ -function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { + logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig }) + + const customCohortsData = deepAccess(segmentData, bidder) || [] + const name = 'permutive.com' const permutiveUserData = { @@ -159,21 +148,59 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation .filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id)) .map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config)) + const customCohortsUserData = { + name: PERMUTIVE_CUSTOM_COHORTS_KEYWORD, + segment: customCohortsData.map(cohortID => ({ id: cohortID })), + } + const ortbConfig = mergeDeep({}, currConfig) const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || [] const updatedUserData = currentUserData - .filter(el => el.name !== name) - .concat(permutiveUserData, transformedUserData) + .filter(el => el.name !== permutiveUserData.name && el.name !== customCohortsUserData.name) + .concat(permutiveUserData, transformedUserData, customCohortsUserData) + logger.logInfo(`Updating ortb2.user.data`, { bidder, user_data: updatedUserData }) deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) - // As of writing this, only used for AppNexus/Xandr in place of appnexusAuctionKeywords in config - const currentUserKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') || '' - const keywords = sspSegmentIDs.map(segment => `${PERMUTIVE_STANDARD_AUD_KEYWORD}=${segment}`).join(',') - const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` - deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + // Set ortb2.user.keywords + const currentKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentIDs, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: sspSegmentIDs, + [PERMUTIVE_CUSTOM_COHORTS_KEYWORD]: customCohortsData, + } + // Transform groups of key-values into a single array of strings + // i.e { permutive: ['1', '2'], p_standard: ['3', '4'] } => ['permutive=1', 'permutive=2', 'p_standard=3',' p_standard=4'] + const transformedKeywordGroups = Object.entries(keywordGroups) + .flatMap(([keyword, ids]) => ids.map(id => `${keyword}=${id}`)) + + const keywords = [ + currentKeywords, + ...transformedKeywordGroups, + ] + .filter(Boolean) + .join(',') + + logger.logInfo(`Updating ortb2.user.keywords`, { + bidder, + keywords, + }) + deepSetValue(ortbConfig, 'ortb2.user.keywords', keywords) + + // Set user extensions + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_STANDARD_KEYWORD}`, segmentIDs) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_STANDARD_KEYWORD}"`, segmentIDs) + } + + if (customCohortsData.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}`, customCohortsData.map(String)) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) + } + + logger.logInfo(`Updated ortb2 config`, { bidder, config: ortbConfig }) return ortbConfig } @@ -251,28 +278,6 @@ function getDefaultBidderFn (bidder) { return [...new Set([...ac, ...ssp])] } const bidderMap = { - appnexus: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.keywords.p_standard', segments) - } - if (data.appnexus && data.appnexus.length) { - deepSetValue(bid, 'params.keywords.permutive', data.appnexus) - } - - return bid - }, - rubicon: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.visitor.p_standard', segments) - } - if (data.rubicon && data.rubicon.length) { - deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) - } - - return bid - }, ozone: function (bid, data, acEnabled) { if (isPStandardTargetingEnabled(data, acEnabled)) { const segments = pStandardTargeting(data, acEnabled) @@ -283,7 +288,12 @@ function getDefaultBidderFn (bidder) { } } - return bidderMap[bidder] + // On no default bidder just return the same bid as passed in + function bidIdentity(bid) { + return bid + } + + return bidderMap[bidder] || bidIdentity } /** @@ -317,6 +327,7 @@ export function getSegments (maxSegs) { const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], + ix: readSegments('_pindexs', []), rubicon: readSegments('_prubicons', []), appnexus: readSegments('_papns', []), gam: readSegments('_pdfps', []), @@ -383,17 +394,55 @@ function iabSegmentId(permutiveSegmentId, iabIds) { return iabIds[permutiveSegmentId] || unknownIabSegmentId } +/** + * Pull the latest configuration and cohort information and update accordingly. + * + * @param reqBidsConfigObj - Bidder provided config for request + * @param customModuleConfig - Publisher provide config + */ +export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { + const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) + + makeSafe(function () { + // Legacy route with custom parameters + // ACK policy violation, in process of removing + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + }); + + makeSafe(function () { + // Route for bidders supporting ORTB2 + setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, moduleConfig, segmentData) + }) +} + +let permutiveSDKInRealTime = false + /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + const completeBidRequestData = () => { + logger.logInfo(`Request data updated`) + callback() + } + + const moduleConfig = getModuleConfig(customModuleConfig) + + readAndSetCohorts(reqBidsConfigObj, moduleConfig) + makeSafe(function () { - // Legacy route with custom parameters - initSegments(reqBidsConfigObj, callback, customModuleConfig) - }); - makeSafe(function () { - // Route for bidders supporting ORTB2 - setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) + if (permutiveSDKInRealTime || !(moduleConfig.waitForIt && isPermutiveOnPage())) { + return completeBidRequestData() + } + + window.permutive.ready(function () { + logger.logInfo(`SDK is realtime, updating cohorts`) + permutiveSDKInRealTime = true + readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) + completeBidRequestData() + }, 'realtime') + + logger.logInfo(`Registered cohort update when SDK is realtime`) }) }, init: init diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 7d8f073357c..9399dffab93 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -13,7 +13,7 @@ gulp build --modules=rtdModule,permutiveRtdProvider > Note that the global RTD module, `rtdModule`, is a prerequisite of the Permutive RTD module. -You then need to enable the Permutive RTD in your Prebid configuration, using the below format: +You then need to enable the Permutive RTD in your Prebid configuration. Below is an example of the format: ```javascript pbjs.setConfig({ @@ -37,59 +37,16 @@ pbjs.setConfig({ The parameters below provide configurability for general behaviours of the RTD submodule, as well as enabling settings for specific use cases mentioned above (e.g. acbidders). -| Name | Type | Description | Default | -|------------------------|----------|-----------------------------------------------------------------------------------------------|---------| -| name | String | This should always be `permutive` | - | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | -| params | Object | | - | -| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | -| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | -| params.transformations | Object[] | An array of configurations for ORTB2 user data transformations | | -| params.overwrites | Object | An object specifying functions for custom targeting logic for bidders. | - | - -##### The `transformations` parameter - -This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests. - -###### Supported transformations - -| Name | ID | Config structure | Description | -|----------------|-----|---------------------------------------------------|--------------------------------------------------------------------------------------| -| IAB taxonomies | iab | { segtax: number, iabIds: Object<number, number>} | Transform segment IDs from Permutive to IAB (note: alpha version, subject to change) | - -##### The `overwrites` parameter - -The keys for this object should match a bidder (e.g. `rubicon`), which then can define a function to overwrite the customer targeting logic. - -```javascript -{ - params: { - overwrites: { - rubicon: function customTargeting(bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } - } - } - } -} -``` - -###### `customTargeting` function parameters - -| Name | Description | -|--------------|--------------------------------------------------------------------------------| -| `bid` | The bid request object. | -| `data` | Permutive's targeting data read from localStorage. | -| `acEnabled` | Boolean stating whether Audience Connect is enabled via `acBidders`. | -| `utils` | An object of helpful utilities. `(deepSetValue, deepAccess, isFn, mergeDeep)`. | -| `defaultFn` | The default targeting function. | - - +## Parameters +{: .table .table-bordered .table-striped } +| Name | Type | Description | Default | +| ---------------------- | -------------------- | --------------------------------------------------------------------------------------------- | ------------------ | +| name | String | This should always be `permutive` | - | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | - | +| params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | +| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | #### Context @@ -100,7 +57,7 @@ As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Per #### Instructions -1. Publisher enables GDPR rules within Prebid. +1. Publisher enables rules within Prebid GDPR module 2. Label Permutive as an exception, as shown below. ```javascript [ @@ -123,29 +80,58 @@ Before making any updates to this configuration, please ensure that this approac ## Cohort Activation with Permutive RTD Module -### _Enabling Standard Cohorts_ - **Note**: Publishers must be enabled on the above Permutive RTD Submodule to enable Standard Cohorts. -The acbidders config in the Permutive RTD module allows publishers to determine which demand partners (SSPs) will receive standard cohorts via the <u>user.data</u> ortb2 object. Cohorts will be sent in the `p_standard` key-value. +### _Enabling Publisher Cohorts_ + +#### Standard Cohorts + +The Permutive RTD module sets Standard Cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. Cohorts will be sent in the `p_standard` key-value. + +For Prebid versions below 7.29.0, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Standard Cohorts with. You also need to permission the bidders by communicating the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -The Permutive RTD module sets standard cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. +For Prebid versions 7.29.0 and above, do not populate bidder codes in acbidders for the purpose of sharing Standard Cohorts (Note: there may be other business needs that require you to populate acbidders for Prebid versions 7.29.0+, see Advertiser Cohorts below). To share Standard Cohorts with bidders in Prebid versions 7.29.0 and above, communicate the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -There are **two** ways to assign which demand partner bidders (e.g. SSPs) will receive Standard Cohort information via the Audience Connector (acbidders) config: +#### _Bidder Specific Requirements for Standard Cohorts_ +For PubMatic or OpenX: Please ensure you are using Prebid.js 7.13 (or later) +For Xandr: Please ensure you are using Prebid.js 7.29 (or later) +For Equativ: Please ensure you are using Prebid.js 7.26 (or later) + +#### Custom Cohorts + +The Permutive RTD module also supports passing any of the **Custom** Cohorts created in the dashboard to some SSP partners for targeting +e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). + +Currently, bidders with known support for custom cohort targeting are: + +- Xandr +- Magnite + +When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. +There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. +Permutive cohorts will be sent in the permutive key-value. + + +### _Enabling Advertiser Cohorts_ + +If you are connecting to an Advertiser seat within Permutive to share Advertiser Cohorts, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Advertiser Cohorts with. + +### _Managing acbidders_ + +If your business needs require you to populate acbidders with bidder codes based on the criteria above, there are **two** ways to manage it. #### Option 1 - Automated -New demand partner bidders may be added to the acbidders config directly within the Permutive Platform. +If you are using Prebid.js v7.13.0+, bidders may be added to or removed from the acbidders config directly within the Permutive Dashboard. **Permutive can do this on your behalf**. Simply contact your Permutive CSM with strategicpartnershipops@permutive.com on cc, indicating which bidders you would like added. -Or, a publisher may do this themselves within the UI using the below instructions. +Or, a publisher may do this themselves within the Permutive Dashboard using the below instructions. ##### Create Integration -In order to update acbidders via the Permutive dashboard, -it is necessary to first enable the prebid integration in the integrations page (settings). +In order to manage acbidders via the Permutive dashboard, it is necessary to first enable the Prebid integration via the integrations page (settings). **Note on Revenue Insights:** The prebid integration includes a feature for revenue insights, which is not required for the purpose of updating acbidders config. @@ -153,31 +139,15 @@ Please see [this document](https://support.permutive.com/hc/en-us/articles/36001 ##### Update acbidders -The input for the “Data Provider config” is currently a multi-input free text. -A valid “bidder code” needs to be entered in order to enable Standard Cohorts to be passed to the desired partner. -The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. +The input for the “Data Provider config” is a multi-input free text. A valid “bidder code” needs to be entered in order to enable Standard or Advertiser Cohorts to be passed to the desired partner. The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. -Acbidders can be added or removed from the list using this feature, however, this will not impact any acbidders that have been applied using the manual method below. +Bidders can be added or removed from acbidders using this feature, however, this will not impact any bidders that have been applied using the manual method below. #### Option 2 - Manual -As a secondary option, new demand partner bidders may be added manually. +As a secondary option, bidders may be added manually. -To do so, a Publisher may define which bidders should receive Standard Cohorts by +To do so, define which bidders should receive Standard or Advertiser Cohorts by including the _bidder code_ of any bidder in the `acBidders` array. -**Note:** If a Publisher ever needs to remove a manually-added bidder, the bidder will also need to be removed manually. - -### _Enabling Custom Cohort IDs for Targeting_ - -Separately from Standard Cohorts - The Permutive RTD module also supports passing any of the **custom** cohorts created in the dashboard to some SSP partners for targeting -e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). - -Currently, bidders with known support for custom cohort targeting are: - -- Xandr -- Magnite - -When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. -There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. -Permutive cohorts will be sent in the permutive key-value. +**Note:** If you ever need to remove a manually-added bidder, the bidder will also need to be removed manually. diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 41b1151e72a..5019b31b90b 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -14,6 +14,7 @@ import { transformBidderParamKeywords } from '../src/utils.js'; import { auctionManager } from '../src/auctionManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); @@ -132,7 +133,7 @@ export const spec = { method: 'POST', options: {withCredentials: true}, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index c5f082f5355..ffb02204ed0 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -20,7 +20,7 @@ import { import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {isValid} from '../../src/adapters/bidderFactory.js'; +import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; @@ -496,6 +496,9 @@ export function PrebidServer() { addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID); } } + }, + onFledge: ({adUnitCode, config}) => { + addComponentAuction(adUnitCode, config); } }) } @@ -521,7 +524,7 @@ export function PrebidServer() { * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse */ -export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) { +export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) { let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); @@ -545,8 +548,11 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request).bids); + const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs.forEach(onFledge); + } } catch (error) { logError(error); } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index be034b3cfbe..820c34c66fd 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -221,6 +221,13 @@ const PBS_CONVERTER = ortbConverter({ serverSideStats(orig, response, ortbResponse, context) { // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); + }, + fledgeAuctionConfigs(orig, response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({adUnitCode: impCtx.adUnit.code, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } } } }, @@ -254,11 +261,14 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste ...adUnit, adUnitCode: adUnit.code, ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), - pbsData: {impId, actualBidRequests, adUnit} + pbsData: {impId, actualBidRequests, adUnit}, }); }); - const proxyBidderRequest = Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))) + const proxyBidderRequest = { + ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), + fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + } return PBS_CONVERTER.toORTB({ bidderRequest: proxyBidderRequest, diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 1fde8f8db5b..a32e26ef6c2 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -10,6 +10,7 @@ import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); @@ -168,7 +169,7 @@ export function getPubcidConfig() { return pubcidConfig; } */ export const requestBidHook = timedAuctionHook('pubCommonId', function requestBidHook(next, config) { - let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; + let adUnits = config.adUnits || getGlobal().adUnits; let pubcid = null; // Pass control to the next function if not enabled @@ -292,7 +293,7 @@ export function initPubcid() { (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); if (!optout) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidHook); + getGlobal().requestBids.before(requestBidHook); } } diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 5e381e74a18..a7381bb2884 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -1,19 +1,32 @@ -import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; -import { config } from '../src/config.js'; + +import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const VERSION = '0.2.0'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const VERSION = '0.3.0'; const GVLID = 842; const NET_REVENUE = true; const UNDEFINED = undefined; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'pwbid'; +const LOG_PREFIX = 'PubWise: '; const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; +// const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; // testing observable endpoint const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'https://prebid.org/dev-docs/show-native-ads.html'; // const USERSYNC_URL = '//127.0.0.1:8080/usersync' +const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + +const MEDIATYPE = [ + BANNER, + VIDEO, + NATIVE +] const CUSTOM_PARAMS = { 'gender': '', // User gender @@ -22,6 +35,32 @@ const CUSTOM_PARAMS = { 'lon': '', // User Location - Longitude }; +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; + +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER +} + // rtb native types are meant to be dynamic and extendable // the extendable data asset types are nicely aligned // in practice we set an ID that is distinct for each real type of return @@ -88,7 +127,7 @@ _each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = a export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. * @@ -96,18 +135,40 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - // siteId is required + // siteId is required for any type if (bid.params && bid.params.siteId) { // it must be a string if (!isStr(bid.params.siteId)) { _logWarn('siteId is required for bid', bid); return false; } - } else { - return false; + + // video ad validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array + let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); + let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); + if (_isNonEmptyArray(mediaTypesVideoMimes) === false && _isNonEmptyArray(paramsVideoMimes) === false) { + _logWarn('Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call suppressed:', JSON.stringify(bid)); + return false; + } + + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + _logError(`no context specified in bid. Rejecting bid: `, JSON.stringify(bid)); + return false; + } + + if (bid.mediaTypes[VIDEO].context === 'outstream') { + delete bid.mediaTypes[VIDEO]; + _logWarn(`outstream not currently supported `, JSON.stringify(bid)); + return false; + } + } + + return true; } - return true; + return false; }, /** * Make a server request from the list of BidRequests. @@ -116,6 +177,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -210,7 +272,7 @@ export const spec = { return { method: 'POST', - url: ENDPOINT_URL, + url: _getEndpointURL(bid), data: payload, options: options, bidderRequest: bidderRequest, @@ -231,6 +293,7 @@ export const spec = { // try { if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + _logInfo('interpretResponse response body', response.body); // Supporting multiple bid responses for same adSize respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { @@ -254,10 +317,24 @@ export const spec = { if (parsedRequest.imp && parsedRequest.imp.length > 0) { parsedRequest.imp.forEach(req => { if (bid.impid === req.id) { - _checkMediaType(bid.adm, newBid); + _checkMediaType(bid, newBid); switch (newBid.mediaType) { case BANNER: break; + case VIDEO: + const videoContext = deepAccess(request, 'mediaTypes.video.context'); + switch (videoContext) { + case OUTSTREAM: + // not currently supported + break; + case INSTREAM: + break; + } + newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; + newBid.vastXml = bid.adm; + newBid.vastUrl = bid.vastUrl; + break; case NATIVE: _parseNativeResponse(bid, newBid); break; @@ -289,20 +366,31 @@ export const spec = { } } -function _checkMediaType(adm, newBid) { - // Create a regex here to check the strings - var admJSON = ''; - if (adm.indexOf('"ver":') >= 0) { - try { - admJSON = JSON.parse(adm.replace(/\\/g, '')); - if (admJSON && admJSON.assets) { - newBid.mediaType = NATIVE; +function _checkMediaType(bid, newBid) { + // Check Various ADM Aspects to Determine Media Type + if (bid.ext && bid.ext['bidtype'] != undefined) { + // this is the most explicity check + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; + } else { + _logInfo('bid.ext.bidtype does not exist, checking alternatively for mediaType'); + var adm = bid.adm; + var videoRegex = new RegExp(/VAST\s+version/); + + if (adm.indexOf('"ver":') >= 0) { + try { + var admJSON = ''; + admJSON = JSON.parse(adm.replace(/\\/g, '')); + if (admJSON && admJSON.assets) { + newBid.mediaType = NATIVE; + } + } catch (e) { + _logWarn('Error: Cannot parse native reponse for ad response: ', adm); } - } catch (e) { - _logWarn('Error: Cannot parse native reponse for ad response: ' + adm); + } else if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; } - } else { - newBid.mediaType = BANNER; } } @@ -416,7 +504,8 @@ function _createOrtbTemplate(conf) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, h: screen.height, w: screen.width, - language: navigator.language + language: navigator.language, + devicetype: _getDeviceType() }, user: {}, ext: { @@ -428,6 +517,7 @@ function _createOrtbTemplate(conf) { function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; + var videoObj; var nativeObj = {}; var mediaTypes = ''; @@ -459,6 +549,12 @@ function _createImpressionObject(bid, conf) { _logWarn('Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); } break; + case VIDEO: + videoObj = _createVideoRequest(bid); + if (videoObj !== UNDEFINED) { + impObj.video = videoObj; + } + break; } } } else { @@ -468,7 +564,8 @@ function _createImpressionObject(bid, conf) { _addFloorFromFloorModule(impObj, bid); return impObj.hasOwnProperty(BANNER) || - impObj.hasOwnProperty(NATIVE) ? impObj : UNDEFINED; + impObj.hasOwnProperty(NATIVE) || + impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } function _parseSlotParam(paramName, paramValue) { @@ -492,7 +589,7 @@ function _parseSlotParam(paramName, paramValue) { } function _parseAdSlot(bid) { - _logInfo('parseAdSlot bid', bid) + _logInfo('parseAdSlot bid', bid); if (bid.adUnitCode) { bid.params.adUnit = bid.adUnitCode; } else { @@ -504,7 +601,7 @@ function _parseAdSlot(bid) { if (bid.hasOwnProperty('mediaTypes')) { if (bid.mediaTypes.hasOwnProperty(BANNER) && - bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes + bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes var i = 0; var sizeArray = []; for (;i < bid.mediaTypes.banner.sizes.length; i++) { @@ -522,7 +619,7 @@ function _parseAdSlot(bid) { } } } else { - _logWarn('MediaTypes are Required for all Adunit Configs', bid) + _logWarn('MediaTypes are Required for all Adunit Configs', bid); } } @@ -558,7 +655,7 @@ function _addFloorFromFloorModule(impObj, bid) { // get lowest floor from floorModule if (typeof bid.getFloor === 'function' && !config.getConfig('pubwise.disableFloors')) { - [BANNER, NATIVE].forEach(mediaType => { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (impObj.hasOwnProperty(mediaType)) { let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' }); if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { @@ -746,28 +843,162 @@ function _createBannerRequest(bid) { _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; } + return bannerObj; } // various error levels are not always used // eslint-disable-next-line no-unused-vars function _logMessage(textValue, objectValue) { - logMessage('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logMessage(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logInfo(textValue, objectValue) { - logInfo('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logInfo(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logWarn(textValue, objectValue) { - logWarn('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logWarn(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logError(textValue, objectValue) { - logError('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logError(LOG_PREFIX + textValue, objectValue); +} + +function _checkVideoPlacement(videoData, adUnitCode) { + // Check for video.placement property. If property is missing display log message. + if (!deepAccess(videoData, 'placement')) { + _logWarn(`${MSG_VIDEO_PLACEMENT_MISSING} for ${adUnitCode}`, adUnitCode); + }; +} + +function _createVideoRequest(bid) { + var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); + var videoObj; + + if (videoData !== UNDEFINED) { + videoObj = {}; + _checkVideoPlacement(videoData, bid.adUnitCode); + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoData.hasOwnProperty(key)) { + videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + // read playersize and assign to h and w. + if (isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); + } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); + } + } else { + videoObj = UNDEFINED; + _logWarn('Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.', bid.params); + } + return videoObj; +} + +/** + * Determines if the array has values + * + * @param {object} test + * @returns {boolean} + */ +function _isNonEmptyArray(test) { + if (isArray(test) === true) { + if (test.length > 0) { + return true; + } + } + return false; +} + +/** + * Returns the overridden bid endpoint_url if it is set, primarily used for testing + * + * @param {object} bid the current bid + * @returns + */ +function _getEndpointURL(bid) { + if (!isEmptyStr(bid?.params?.endpoint_url) && bid?.params?.endpoint_url != UNDEFINED) { + return bid.params.endpoint_url; + } + + return ENDPOINT_URL; +} + +/** + * + * @param {object} key + * @param {object}} value + * @param {object} datatype + * @returns + */ +function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + _logWarn(errMsg, key); + return UNDEFINED; +} + +function _isMobile() { + if (navigator.userAgentData && navigator.userAgentData.mobile) { + return true; + } else { + return (/(mobi)/i).test(navigator.userAgent); + } +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _isTablet() { + return (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase())); +} + +/** + * Very high level device detection, order matters + */ +function _getDeviceType() { + if (_isTablet()) { + return 5; + } + + if (_isMobile()) { + return 4; + } + + if (_isConnectedTV()) { + return 3; + } + + return 2; } // function _decorateLog() { @@ -777,6 +1008,7 @@ function _logError(textValue, objectValue) { // these are exported only for testing so maintaining the JS convention of _ to indicate the intent export { + _checkVideoPlacement, _checkMediaType, _parseAdSlot } diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 96665c786bf..19a3c236942 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -180,7 +181,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version } }); if (status == 'bidwon') { diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index cf14ff44571..c74ce519ab9 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -2,6 +2,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; @@ -20,7 +21,7 @@ rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page rivrAnalytics.enableAnalytics = (config) => { if (window.rivraddon && window.rivraddon.analytics) { - window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: $$PREBID_GLOBAL$$}); + window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: getGlobal()}); rivrAnalytics.originEnableAnalytics(config); } }; diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 9f498014a8e..5c3d430aadf 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -56,7 +56,7 @@ export const spec = { site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, test: validBidRequests[0].params.test || 0, - source: mapSource(validBidRequests[0]), + source: mapSource(validBidRequests[0], bidderRequest), }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { @@ -171,10 +171,12 @@ export const spec = { if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {} + }, cfg) + } }); logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { @@ -228,6 +230,13 @@ function mapImpression(slot, bidderRequest) { delete imp.ext.ae; } } + + const tid = deepAccess(slot, 'ortb2Imp.ext.tid'); + if (tid) { + imp.ext = imp.ext || {}; + imp.ext.tid = tid; + } + return imp; } @@ -283,9 +292,9 @@ function mapSite(slot, bidderRequest) { * @param {object} slot Ad Unit Params by Prebid * @returns {object} Source by OpenRTB 2.5 §3.2.2 */ -function mapSource(slot) { +function mapSource(slot, bidderRequest) { const source = { - tid: slot.transactionId, + tid: bidderRequest?.auctionId || '', }; return source; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 293b0d51b33..11d60e77ece 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -123,6 +123,7 @@ var sizeMap = { 278: '320x500', 282: '320x400', 288: '640x380', + 524: '1x2', 548: '500x1000', 550: '980x480', 552: '300x200', @@ -392,7 +393,6 @@ export const spec = { 'tk_flint', 'x_source.tid', 'l_pb_bid_id', - 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', @@ -466,7 +466,6 @@ export const spec = { 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, - 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index f9ce3a450cf..4fa95e4ba51 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -38,7 +38,9 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; + const pageURL = validBidRequests[0].params.contentPageUrl || + bidderRequest.refererInfo.canonicalUrl || + deepAccess(window, 'location.href'); const isStage = !!validBidRequests[0].params.stage; const isViralize = !!validBidRequests[0].params.unitId; const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; @@ -50,6 +52,7 @@ export const spec = { const defaultSchain = validBidRequests[0].schain || {}; const consentData = bidderRequest.gdprConsent || {}; + const uspConsent = bidderRequest.uspConsent || ''; const gdprConsent = { apiVersion: consentData.apiVersion || 2, gdprApplies: consentData.gdprApplies || 0, @@ -104,6 +107,7 @@ export const spec = { height: size[1] }; rBid.gdprConsent = gdprConsent; + rBid.uspConsent = uspConsent; } return rBid; @@ -138,6 +142,7 @@ export const spec = { 'bidRequests': adUnits, 'context': { 'gdprConsent': gdprConsent, + 'uspConsent': uspConsent, 'schain': defaultSchain, 'pageURL': QA.pageURL || encodeURIComponent(pageURL) } diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 05475b3e143..d212d98f50b 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -182,7 +182,7 @@ export function checkAdUnitSetupHook(adUnits) { } } - if (mediaTypes.video) { + if (FEATURES.VIDEO && mediaTypes.video) { if (mediaTypes.video.playerSize) { // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 8c2d9d4ff17..c1eb1bb8489 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -68,11 +68,23 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); } + if (ortb2.device?.ifa !== undefined) { + deepSetValue(requestTemplate, 'device.ifa', ortb2.device.ifa); + } + + if (ortb2.device?.geo !== undefined) { + deepSetValue(requestTemplate, 'device.geo', ortb2.device.geo); + } + if (deepAccess(bidRequest, 'params.app')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(requestTemplate, 'device.geo', geo); - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(requestTemplate, 'device.ifa', ifa); + if (!deepAccess(requestTemplate, 'device.geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(requestTemplate, 'device.geo', geo); + } + if (!deepAccess(requestTemplate, 'device.ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(requestTemplate, 'device.ifa', ifa); + } } const eids = deepAccess(bidRequest, 'userIdAsEids'); diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index e5800e7cad0..89749aed433 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -17,7 +17,7 @@ function isBidResponseValid(bid) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: - return Boolean(bid.vastUrl); + return Boolean(bid.vastUrl) || Boolean(bid.vastXml); case NATIVE: return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); default: diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 9f275a761c7..34cf9285909 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -57,6 +57,7 @@ export const spec = { const bidRequests = validBidRequests.map((validBidRequest) => { let video = deepAccess(validBidRequest, 'mediaTypes.video', false); let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + let sizes = validBidRequest.params.sizes; let oneRequest = { endpointId: validBidRequest.params.endpointId, @@ -67,8 +68,16 @@ export const spec = { if (video) { oneRequest.video = video; + + if (sizes) { + oneRequest.video.sizes = sizes; + } } else if (banner) { oneRequest.banner = banner; + + if (sizes) { + oneRequest.banner.sizes = sizes; + } } return oneRequest diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index e0ec10c6ed6..f41fb98d436 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; @@ -33,7 +34,7 @@ export const spec = { test: getTestFlag(), devw: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, devh: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, - version: $$PREBID_GLOBAL$$.version, + version: getGlobal().version, gdprApplies: gdprApplies, gdprConsentString: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.consentString') : undefined, gdprConsentProv: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.addtlConsent') : undefined, diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 87358705cb5..6760a3c18ab 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; +import { bidderSettings } from '../src/bidderSettings.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -49,6 +50,7 @@ export const spec = { return true; }, + /** * Make a server request from the list of BidRequests. * @@ -93,7 +95,7 @@ export const spec = { 'lib_name': 'prebid', 'lib_v': '$prebid.version$', 'us': 0, - + 'iqid': bidderSettings.get(BIDDER_CODE, 'storageAllowed') ? JSON.stringify(loadOrCreateFirstPartyData()) : null, }; const fpd = bidderRequest.ortb2; @@ -388,6 +390,67 @@ export function _getPlatform(context = window) { } return 'desktop'; } +/** + * Check for local storage + * Generate a UUID for the user if one does not exist in local storage + * Store the UUID in local storage for future use + * @return {object} firstPartyData - Data object containing first party information + */ +function loadOrCreateFirstPartyData() { + var localStorageEnabled; + + var FIRST_PARTY_KEY = '_iiq_fdata'; + var tryParse = function (data) { + try { + return JSON.parse(data); + } catch (err) { + return null; + } + }; + var readData = function (key) { + if (hasLocalStorage()) { + return window.localStorage.getItem(key); + } + return null; + }; + var hasLocalStorage = function () { + if (typeof localStorageEnabled != 'undefined') { return localStorageEnabled; } else { + try { + localStorageEnabled = !!window.localStorage; + return localStorageEnabled; + } catch (e) { + localStorageEnabled = false; + } + } + return false; + }; + var generateGUID = function () { + var d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); + }; + var storeData = function (key, value) { + try { + if (hasLocalStorage()) { + window.localStorage.setItem(key, value); + } + } catch (error) { + return null; + } + }; + var firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); + if (!firstPartyData || !firstPartyData.pcid) { + var firstPartyId = generateGUID(); + firstPartyData = { pcid: firstPartyId, pcidDate: Date.now() }; + } else if (firstPartyData && !firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + } + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + return firstPartyData; +}; function newRenderer(adUnitCode, bid, rendererOptions = {}) { const renderer = Renderer.install({ diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js new file mode 100644 index 00000000000..32a3d289c61 --- /dev/null +++ b/modules/stvBidAdapter.js @@ -0,0 +1,330 @@ +import { deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {includes} from '../src/polyfill.js'; + +const BIDDER_CODE = 'stv'; +const ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; +const ENDPOINT_URL_DEV = 'https://ads.smartstream.tv/r/'; +const GVLID = 134; +const VIDEO_ORTB_PARAMS = { + 'minduration': 'min_duration', + 'maxduration': 'max_duration', + 'maxbitrate': 'max_bitrate', + 'api': 'api', +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function(bid) { + return !!(bid.params.placement); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + + const placementId = params.placement; + const rnd = Math.floor(Math.random() * 99999999999); + const referrer = bidderRequest.refererInfo.page; + const bidId = bidRequest.bidId; + const isDev = params.devMode || false; + const pbcode = bidRequest.adUnitCode || false; // div id + + let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; + + let mediaTypesInfo = getMediaTypesInfo(bidRequest); + let type = isBannerRequest(bidRequest) ? BANNER : VIDEO; + let sizes = mediaTypesInfo[type]; + + let payload = { + _f: 'vast2', + alternative: 'prebid_js', + _ps: placementId, + srw: sizes ? sizes[0].width : 0, + srh: sizes ? sizes[0].height : 0, + idt: 100, + rnd: rnd, + ref: referrer, + bid_id: bidId, + pbver: '$prebid.version$', + }; + if (!isVideoRequest(bidRequest)) { + payload._f = 'html'; + } + + payload.pfilter = { ...params }; + delete payload.pfilter.placement; + if (params.bcat !== undefined) { delete payload.pfilter.bcat; } + if (params.dvt !== undefined) { delete payload.pfilter.dvt; } + if (params.devMode !== undefined) { delete payload.pfilter.devMode; } + + if (payload.pfilter === undefined || !payload.pfilter.floorprice) { + let bidFloor = getBidFloor(bidRequest); + if (bidFloor > 0) { + if (payload.pfilter !== undefined) { + payload.pfilter.floorprice = bidFloor; + } else { + payload.pfilter = { 'floorprice': bidFloor }; + } + // payload.bidFloor = bidFloor; + } + } + + if (mediaTypesInfo[VIDEO] !== undefined) { + let videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + Object.keys(videoParams) + .filter(key => includes(Object.keys(VIDEO_ORTB_PARAMS), key) && params[VIDEO_ORTB_PARAMS[key]] === undefined) + .forEach(key => payload.pfilter[VIDEO_ORTB_PARAMS[key]] = videoParams[key]); + } + if (Object.keys(payload.pfilter).length == 0) { delete payload.pfilter } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + + if (params.bcat !== undefined) { + payload.bcat = deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat; + } + if (params.dvt !== undefined) { + payload.dvt = params.dvt; + } + if (isDev) { + payload.prebidDevMode = 1; + } + + if (pbcode) { + payload.pbcode = pbcode; + } + + payload.media_types = convertMediaInfoForRequest(mediaTypesInfo); + + return { + method: 'GET', + url: endpoint, + data: objectToQueryString(payload), + }; + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.crid || 0; + const cpm = response.cpm / 1000000 || 0; + if (cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'EUR'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.bid_id, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + meta: { + advertiserDomains: response.adomain || [] + } + }; + if (response.vastXml) { + bidResponse.vastXml = response.vastXml; + bidResponse.mediaType = 'video'; + } else { + bidResponse.ad = response.adTag; + } + + bidResponses.push(bidResponse); + } + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = [] + + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (serverResponses.length > 0 && serverResponses[0].body !== undefined && + serverResponses[0].body.userSync !== undefined && serverResponses[0].body.userSync.iframeUrl !== undefined && + serverResponses[0].body.userSync.iframeUrl.length > 0) { + if (syncOptions.iframeEnabled) { + serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ + type: 'iframe', + url: appendToUrl(url, gdprParams) + })); + } + if (syncOptions.pixelEnabled) { + serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ + type: 'image', + url: appendToUrl(url, gdprParams) + })); + } + } + return syncs; + } +} + +function appendToUrl(url, what) { + if (!what) { + return url; + } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; +} + +function objectToQueryString(obj, prefix) { + let str = []; + let p; + for (p in obj) { + if (obj.hasOwnProperty(p)) { + let k = prefix ? prefix + '[' + p + ']' : p; + let v = obj[p]; + str.push((v !== null && typeof v === 'object') + ? objectToQueryString(v, k) + : encodeURIComponent(k) + '=' + encodeURIComponent(v)); + } + } + return str.join('&'); +} + +/** + * Check if it's a banner bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a banner bid + */ +function isBannerRequest(bid) { + return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); +} + +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +/** + * Get video sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +/** + * Get banner sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +/** + * Parse size + * @param sizes + * @returns {width: number, h: height} + */ +function parseSize(size) { + let sizeObj = {} + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + return sizeObj; +} + +/** + * Parse sizes + * @param sizes + * @returns {{width: number , height: number }[]} + */ +function parseSizes(sizes) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parseSize(size)); + } + return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) +} + +/** + * Get MediaInfo object for server request + * + * @param mediaTypesInfo + * @returns {*} + */ +function convertMediaInfoForRequest(mediaTypesInfo) { + let requestData = {}; + Object.keys(mediaTypesInfo).forEach(mediaType => { + requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { + return size.width + 'x' + size.height; + }).join(','); + }); + return requestData; +} + +/** + * Get Bid Floor + * @param bid + * @returns {number|*} + */ +function getBidFloor(bid) { + if (typeof bid.getFloor !== 'function') { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +/** + * Get media types info + * + * @param bid + */ +function getMediaTypesInfo(bid) { + let mediaTypesInfo = {}; + + if (bid.mediaTypes) { + Object.keys(bid.mediaTypes).forEach(mediaType => { + if (mediaType === BANNER) { + mediaTypesInfo[mediaType] = getBannerSizes(bid); + } + if (mediaType === VIDEO) { + mediaTypesInfo[mediaType] = getVideoSizes(bid); + } + }); + } else { + mediaTypesInfo[BANNER] = getBannerSizes(bid); + } + return mediaTypesInfo; +} + +registerBidder(spec); diff --git a/modules/stvBidAdapter.md b/modules/stvBidAdapter.md new file mode 100644 index 00000000000..b9bce0fd18a --- /dev/null +++ b/modules/stvBidAdapter.md @@ -0,0 +1,62 @@ +# Overview + +``` +Module Name: STV/Smartstream Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smartstream.tv +``` + +# Description + +STV/Smartstream adapter for Prebid. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ] + } + }, + bids: [ + { + bidder: "stv", + params: { + placement: '101', // [required] info available from your contact with Smartstream team + /* // [optional params] + bcat: "IAB2,IAB4", // [optional] list of blocked advertiser categories (IAB), comma separated + */ + } + } + ] + }, + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'stv', + params: { + placement: '106', + /* // [optional params] + bcat: "IAB2,IAB4", // [optional] list of blocked advertiser categories (IAB), comma separated + floorprice: 1000000, // input min_cpm_micros, CPM in EUR * 1000000 + max_duration: 60, // in seconds + min_duration: 5, // in seconds + max_bitrate: 600, + api: [1,2], // https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/master/AdCOM%20v1.0%20FINAL.md#list--api-frameworks- + */ + } + }] + } + ]; +``` diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 026d5a098df..a833d8ec552 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -278,7 +278,7 @@ function getBid(bids, currency, bidResponse) { if (!bidResponse) { return; } - const { + let { price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; let requestId = bids[bidResponse.impid - 1].bidId; @@ -286,6 +286,8 @@ function getBid(bids, currency, bidResponse) { meta.advertiserDomains = advertiserDomains } + ad = replaceAuctionPrice(ad, cpm); + return { requestId, ttl, diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 1a189f5271d..a9d08415090 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -6,6 +6,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import {parseDomain} from '../src/refererDetection.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; @@ -473,7 +474,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { payload.regs = regs; // < Payload - let pbjsv = ($$PREBID_GLOBAL$$.version !== null) ? encodeURIComponent($$PREBID_GLOBAL$$.version) : -1; + let pbjsv = (getGlobal().version !== null) ? encodeURIComponent(getGlobal().version) : -1; return { method: 'POST', diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 9d215f0ceda..c17948d73d0 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -123,7 +124,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, terceptAnalyticsVersion: terceptAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version } }); diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 53fe83b1dd0..233d5f080a9 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,13 +1,34 @@ -import { logMessage, flatten, parseSizesInput } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { + logMessage, + flatten, + parseSizesInput, + isGptPubadsDefined, + isSlotMatchingAdUnitCode, + logInfo, + logWarn, + getWindowSelf, + getWindowTop, + deepAccess +} from '../src/utils.js'; +import { + config +} from '../src/config.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'underdogmedia'; -const UDM_ADAPTER_VERSION = '3.5V'; +const UDM_ADAPTER_VERSION = '7.30V'; const UDM_VENDOR_ID = '159'; const prebidVersion = '$prebid.version$'; +const NON_MEASURABLE = -1; +const PRODUCT = { + standard: 1, + sticky: 2 +} + let USER_SYNCED = false; -logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20191028`); +logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 2023 01 26`); // helper function for testing user syncs export function resetUserSync() { @@ -15,53 +36,117 @@ export function resetUserSync() { } export const spec = { + NON_MEASURABLE, code: BIDDER_CODE, bidParams: [], isBidRequestValid: function (bid) { + if (!bid.params) { + logWarn('[Underdog Media] bid params are missing') + return false; + } + + if (!bid.params.siteId) { + logWarn('[Underdog Media] siteId is missing') + return false; + } + + if (bid.params.productId) { + if (!PRODUCT[bid.params.productId]) { + logWarn('[Underdog Media] invalid productId') + return false; + } + } + const bidSizes = bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? bid.mediaTypes.banner.sizes : bid.sizes; - return !!((bid.params && bid.params.siteId) && (bidSizes && bidSizes.length > 0)); + if (!bidSizes || bidSizes.length < 1) { + logWarn('[Underdog Media] bid sizes are missing') + return false; + } + + return true; }, buildRequests: function (validBidRequests, bidderRequest) { var sizes = []; var siteId = 0; + let data = { + dt: 10, + gdpr: {}, + pbTimeout: config.getConfig('bidderTimeout'), + pbjsVersion: prebidVersion, + placements: [], + ref: deepAccess(bidderRequest, 'refererInfo.ref') ? bidderRequest.refererInfo.ref : undefined, + usp: {}, + userIds: { + '33acrossId': deepAccess(validBidRequests[0], 'userId.33acrossId.envelope') ? validBidRequests[0].userId['33acrossId'].envelope : undefined, + pubcid: deepAccess(validBidRequests[0], 'crumbs.pubcid') ? validBidRequests[0].crumbs.pubcid : undefined, + unifiedId: deepAccess(validBidRequests[0], 'userId.tdid') ? validBidRequests[0].userId.tdid : undefined + }, + version: UDM_ADAPTER_VERSION + } + validBidRequests.forEach(bidParam => { + let placementObject = {} let bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes; sizes = flatten(sizes, parseSizesInput(bidParamSizes)); - siteId = bidParam.params.siteId; + siteId = +bidParam.params.siteId; + let adUnitCode = bidParam.adUnitCode + let element = _getAdSlotHTMLElement(adUnitCode) + let minSize = _getMinSize(bidParamSizes) + + placementObject.sizes = parseSizesInput(bidParamSizes) + placementObject.adUnitCode = adUnitCode + placementObject.productId = PRODUCT[bidParam.params.productId] || PRODUCT.standard + if (deepAccess(bidParam, 'params.productId')) { + if (bidParam.params.productId === 'standard') { + placementObject.productId = 1 + } else if (bidParam.params.productId === 'adhesion') { + placementObject.productId = 2 + } + } else { + placementObject.productId = 1 + } + placementObject.gpid = deepAccess(bidParam, 'ortb2Imp.ext.gpid') ? bidParam.ortb2Imp.ext.gpid : undefined + + if (_isViewabilityMeasurable(element)) { + const minSizeObj = { + w: minSize[0], + h: minSize[1] + } + let viewPercentage = Math.round(_getViewability(element, getWindowTop(), minSizeObj)) + placementObject.viewability = viewPercentage + } else { + placementObject.viewability = NON_MEASURABLE + } + + data.placements.push(placementObject) }); - let data = { - tid: 1, - dt: 10, - sid: siteId, - sizes: sizes.join(','), - version: UDM_ADAPTER_VERSION - } + data.sid = siteId if (bidderRequest && bidderRequest.gdprConsent) { if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - data.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); + data.gdpr.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); } if (bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents && typeof bidderRequest.gdprConsent.vendorData.vendorConsents[UDM_VENDOR_ID] !== 'undefined') { - data.consentGiven = !!(bidderRequest.gdprConsent.vendorData.vendorConsents[UDM_VENDOR_ID]); + data.gdpr.consentGiven = !!(bidderRequest.gdprConsent.vendorData.vendorConsents[UDM_VENDOR_ID]); } if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { - data.consentData = bidderRequest.gdprConsent.consentString; + data.gdpr.consentData = bidderRequest.gdprConsent.consentString; } } if (bidderRequest.uspConsent) { - data.uspConsent = bidderRequest.uspConsent; + data.usp.uspConsent = bidderRequest.uspConsent; } - if (!data.gdprApplies || data.consentGiven) { + if (!data.gdpr || !data.gdpr.gdprApplies || data.gdpr.consentGiven) { return { - method: 'GET', - url: 'https://udmserve.net/udm/img.fetch', + method: 'POST', + url: `https://udmserve.net/udm/img.fetch?sid=${siteId}`, data: data, bidParams: validBidRequests }; @@ -73,7 +158,9 @@ export const spec = { USER_SYNCED = true; const userSyncs = serverResponses[0].body.userSyncs; const syncs = userSyncs.filter(sync => { - const {type} = sync; + const { + type + } = sync; if (syncOptions.iframeEnabled && type === 'iframe') { return true } @@ -87,63 +174,198 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; - bidRequest.bidParams.forEach(bidParam => { - serverResponse.body.mids.forEach(mid => { - if (mid.useCount > 0) { - return; - } - - if (!mid.useCount) { - mid.useCount = 0; + const mids = serverResponse.body.mids + mids.forEach(mid => { + const bidParam = bidRequest.bidParams.find((bidParam) => { + if (mid.ad_unit_code === bidParam.adUnitCode) { + return true } + }) - var sizeNotFound = true; - const bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes; - parseSizesInput(bidParamSizes).forEach(size => { - if (size === mid.width + 'x' + mid.height) { - sizeNotFound = false; - } - }); - - if (sizeNotFound) { - return; - } + if (!bidParam) { + return + } - const bidResponse = { - requestId: bidParam.bidId, - bidderCode: spec.code, - cpm: parseFloat(mid.cpm), - width: mid.width, - height: mid.height, - ad: mid.ad_code_html, - creativeId: mid.mid, - currency: 'USD', - netRevenue: false, - ttl: mid.ttl || 60, - meta: { - advertiserDomains: mid.advertiser_domains || [] - } - }; - - if (bidResponse.cpm <= 0) { - return; - } - if (bidResponse.ad.length <= 0) { - return; + const bidResponse = { + requestId: bidParam.bidId, + bidderCode: spec.code, + cpm: parseFloat(mid.cpm), + width: mid.width, + height: mid.height, + ad: mid.ad_code_html, + creativeId: mid.mid, + currency: 'USD', + netRevenue: false, + ttl: mid.ttl || 60, + meta: { + advertiserDomains: mid.advertiser_domains || [] } + }; - mid.useCount++; + if (bidResponse.cpm <= 0) { + return; + } + if (bidResponse.ad.length <= 0) { + return; + } - bidResponse.ad += makeNotification(bidResponse, mid, bidParam); + bidResponse.ad += makeNotification(bidResponse, mid, bidParam); - bidResponses.push(bidResponse); - }); + bidResponses.push(bidResponse); }); return bidResponses; }, }; +function _getMinSize(bidParamSizes) { + return bidParamSizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min) +} + +function _getAdSlotHTMLElement(adUnitCode) { + return document.getElementById(adUnitCode) || + document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +} + +function _mapAdUnitPathToElementId(adUnitCode) { + if (isGptPubadsDefined()) { + // eslint-disable-next-line no-undef + const adSlots = googletag.pubads().getSlots(); + const isMatchingAdSlot = isSlotMatchingAdUnitCode(adUnitCode); + + for (let i = 0; i < adSlots.length; i++) { + if (isMatchingAdSlot(adSlots[i])) { + const id = adSlots[i].getSlotElementId(); + + logInfo(`[Underdogmedia Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + + return id; + } + } + } + + logWarn(`[Underdogmedia Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + + return null; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getViewability(element, topWin, { + w, + h +} = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { + w, + h + }) + : 0 +} + +function _getPercentInView(element, topWin, { + w, + h +} = {}) { + const elementBoundingBox = _getBoundingBox(element, { + w, + h + }); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([{ + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, + elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +function _getBoundingBox(element, { + w, + h +} = {}) { + let { + width, + height, + left, + top, + right, + bottom + } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { + width, + height, + left, + top, + right, + bottom + }; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + function makeNotification(bid, mid, bidParam) { let url = mid.notification_url; diff --git a/modules/userId/index.js b/modules/userId/index.js index 82ebaf2b14e..4c8d4c0be38 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -133,13 +133,15 @@ import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import {hook, module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js'; -import {getCoreStorageManager} from '../../src/storageManager.js'; +import {getCoreStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE} from '../../src/storageManager.js'; import { cyrb53Hash, deepAccess, + deepSetValue, delayExecution, getPrebidInternal, isArray, + isEmpty, isEmptyStr, isFn, isGptPubadsDefined, @@ -147,8 +149,7 @@ import { isPlainObject, logError, logInfo, - logWarn, - isEmpty, deepSetValue + logWarn } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, GreedyPromise} from '../../src/utils/promise.js'; @@ -158,8 +159,8 @@ import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetr import {findRootDomain} from '../../src/fpd/rootDomain.js'; const MODULE_NAME = 'User ID'; -const COOKIE = 'cookie'; -const LOCAL_STORAGE = 'html5'; +const COOKIE = STORAGE_TYPE_COOKIES; +const LOCAL_STORAGE = STORAGE_TYPE_LOCALSTORAGE; const DEFAULT_SYNC_DELAY = 500; const NO_AUCTION_DELAY = 0; const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 3f3bb66d8ae..44538e30921 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -138,6 +138,12 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index ee4d9708f15..a066d93d69e 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -204,16 +204,16 @@ export const spec = { }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data - timeoutData.forEach(({ params }) => { + const dataToSend = timeoutData.map(({ params, timeout }) => { + const data = { timeout }; if (params) { - params.forEach((item) => { - if (item && item.uid) { - item.uid = parseInt(item.uid); - } + data.params = params.map((item) => { + return item && item.uid ? { uid: parseInt(item.uid) } : {}; }); } + return data; }); - triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData)); + triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(dataToSend)); } }; diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index f73b4369869..2870229f1db 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -1,17 +1,50 @@ -import { formatQS, deepAccess, triggerPixel, isArray, isNumber } from '../src/utils.js'; +import { formatQS, deepAccess, triggerPixel, _each, _map } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js' +import { BANNER, NATIVE } from '../src/mediaTypes.js' +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'yandex'; -const BIDDER_URL = 'https://bs.yandex.ru/metadsp'; +const BIDDER_URL = 'https://bs.yandex.ru/prebid'; const DEFAULT_TTL = 180; const DEFAULT_CURRENCY = 'EUR'; +const SUPPORTED_MEDIA_TYPES = [ BANNER, NATIVE ]; const SSP_ID = 10500; +const IMAGE_ASSET_TYPES = { + ICON: 1, + IMAGE: 3, +}; +const DATA_ASSET_TYPES = { + TITLE: 0, + SPONSORED: 1, + DESC: 2, + RATING: 3, + LIKES: 4, + ADDRESS: 9, + DESC2: 10, + DISPLAY_URL: 11, + CTA_TEXT: 12, + E_504: 504, +}; +export const NATIVE_ASSETS = { + title: [1, DATA_ASSET_TYPES.TITLE], + body: [2, DATA_ASSET_TYPES.DESC], + body2: [3, DATA_ASSET_TYPES.DESC2], + sponsoredBy: [4, DATA_ASSET_TYPES.SPONSORED], + icon: [5, IMAGE_ASSET_TYPES.ICON], + image: [6, IMAGE_ASSET_TYPES.IMAGE], + displayUrl: [7, DATA_ASSET_TYPES.DISPLAY_URL], + cta: [8, DATA_ASSET_TYPES.CTA_TEXT], + rating: [9, DATA_ASSET_TYPES.RATING], + likes: [10, DATA_ASSET_TYPES.LIKES], +} +const NATIVE_ASSETS_IDS = {}; +_each(NATIVE_ASSETS, (asset, key) => { NATIVE_ASSETS_IDS[asset[0]] = key }); + export const spec = { code: BIDDER_CODE, aliases: ['ya'], // short code - supportedMediaTypes: [ BANNER ], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function(bid) { const { params } = bid; @@ -22,13 +55,11 @@ export const spec = { if (!(pageId && impId)) { return false; } - const sizes = bid.mediaTypes?.banner?.sizes; - return isArray(sizes) && isArray(sizes[0]) && isNumber(sizes[0][0]) && isNumber(sizes[0][1]); + return true; }, buildRequests: function(validBidRequests, bidderRequest) { - const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); - const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); let referrer = ''; let domain = ''; @@ -48,9 +79,6 @@ export const spec = { return validBidRequests.map((bidRequest) => { const { params } = bidRequest; const { targetRef, withCredentials = true } = params; - const sizes = bidRequest.mediaTypes.banner.sizes; - const size = sizes[0]; - const [ w, h ] = size; const { pageId, impId } = extractPlacementIds(params); @@ -59,28 +87,24 @@ export const spec = { 'target-ref': targetRef || domain, 'ssp-id': SSP_ID, }; - if (gdprApplies !== undefined) { + + const gdprApplies = Boolean(deepAccess(bidderRequest, 'gdprConsent.gdprApplies')); + if (gdprApplies) { + const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); queryParams['gdpr'] = 1; queryParams['tcf-consent'] = consentString; } const imp = { id: impId, - banner: { - format: sizes, - w, - h, - } + banner: mapBanner(bidRequest), + native: mapNative(bidRequest), }; - if (bidRequest.getFloor) { - const floorInfo = bidRequest.getFloor({ - currency: DEFAULT_CURRENCY, - size - }); - - imp.bidfloor = floorInfo.floor; - imp.bidfloorcur = floorInfo.currency; + const bidfloor = getBidfloor(bidRequest); + if (bidfloor) { + imp.bidfloor = bidfloor.floor; + imp.bidfloorcur = bidfloor.currency; } const queryParamsString = formatQS(queryParams); @@ -91,8 +115,9 @@ export const spec = { id: bidRequest.bidId, imp: [imp], site: { - ref_url: referrer, - page_url: page, + ref: referrer, + page, + domain, }, tmax: timeout, }, @@ -104,58 +129,27 @@ export const spec = { }); }, - interpretResponse: function(serverResponse, {bidRequest}) { - let response = serverResponse.body; - if (!response.seatbid) { - return []; - } - - const { cur, seatbid } = serverResponse.body; - const rtbBids = seatbid - .map(seatbid => seatbid.bid) - .reduce((a, b) => a.concat(b), []); - - return rtbBids.map(rtbBid => { - let prBid = { - requestId: bidRequest.bidId, - cpm: rtbBid.price, - currency: cur || DEFAULT_CURRENCY, - width: rtbBid.w, - height: rtbBid.h, - creativeId: rtbBid.adid, - nurl: rtbBid.nurl, - - netRevenue: true, - ttl: DEFAULT_TTL, - - meta: { - advertiserDomains: rtbBid.adomain && rtbBid.adomain.length > 0 ? rtbBid.adomain : [], - } - }; - - prBid.ad = rtbBid.adm; - - return prBid; - }); - }, + interpretResponse: interpretResponse, onBidWon: function (bid) { let nurl = bid['nurl']; + if (!nurl) { return; } - const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - const curr = (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) - ? bid.originalCurrency - : bid.currency; - - nurl = nurl - .replace(/\${AUCTION_PRICE}/, cpm) - .replace(/\${AUCTION_CURRENCY}/, curr) - ; + let cpm, currency; + if (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) { + cpm = bid.originalCpm; + currency = bid.originalCurrency; + } else { + cpm = bid.cpm; + currency = bid.currency; + } + cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || cpm; - triggerPixel(nurl); + const pixel = replaceAuctionPrice(nurl, cpm, currency); + triggerPixel(pixel); } } @@ -193,4 +187,198 @@ function extractPlacementIds(bidRequestParams) { return result; } +function getBidfloor(bidRequest) { + const floors = []; + + if (typeof bidRequest.getFloor === 'function') { + SUPPORTED_MEDIA_TYPES.forEach(type => { + if (bidRequest.hasOwnProperty(type)) { + const floorInfo = bidRequest.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: type, + size: bidRequest.sizes || '*' } + ) + floors.push(floorInfo); + } + }); + } + + return floors.sort((a, b) => b.floor - a.floor)[0]; +} + +function mapBanner(bidRequest) { + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + const sizes = bidRequest.sizes || bidRequest.mediaTypes.banner.sizes; + const format = sizes.map((size) => ({ + w: size[0], + h: size[1], + })); + const { w, h } = format[0]; + + return { + format, + w, + h, + } + } +} + +function mapNative(bidRequest) { + const adUnitNativeAssets = deepAccess(bidRequest, 'mediaTypes.native'); + if (adUnitNativeAssets) { + const assets = []; + + Object.keys(adUnitNativeAssets).forEach((assetCode) => { + if (NATIVE_ASSETS.hasOwnProperty(assetCode)) { + const nativeAsset = NATIVE_ASSETS[assetCode]; + const adUnitAssetParams = adUnitNativeAssets[assetCode]; + const asset = mapAsset(assetCode, adUnitAssetParams, nativeAsset); + assets.push(asset); + } + }); + + return { + ver: 1.1, + request: JSON.stringify({ + ver: 1.1, + assets + }), + }; + } +} + +function mapAsset(assetCode, adUnitAssetParams, nativeAsset) { + const [ nativeAssetId, nativeAssetType ] = nativeAsset; + const asset = { + id: nativeAssetId, + }; + + if (adUnitAssetParams.required) { + asset.required = 1; + } + + if (assetCode === 'title') { + asset.title = { + len: adUnitAssetParams.len || 25, + }; + } else if (assetCode === 'image' || assetCode === 'icon') { + asset.img = mapImageAsset(adUnitAssetParams, nativeAssetType); + } else { + asset.data = { + type: nativeAssetType, + len: adUnitAssetParams.len, + }; + } + + return asset; +} + +function mapImageAsset(adUnitImageAssetParams, nativeAssetType) { + const img = { + type: nativeAssetType, + }; + + if (adUnitImageAssetParams.aspect_ratios) { + const ratio = adUnitImageAssetParams.aspect_ratios[0]; + const minWidth = ratio.min_width || 100; + + img.wmin = minWidth; + img.hmin = (minWidth / ratio.ratio_width * ratio.ratio_height); + } + + if (adUnitImageAssetParams.sizes) { + const size = Array.isArray(adUnitImageAssetParams.sizes[0]) ? adUnitImageAssetParams.sizes[0] : adUnitImageAssetParams.sizes; + img.w = size[0]; + img.h = size[1]; + } + + return img; +} + +function interpretResponse(serverResponse, { bidRequest }) { + let response = serverResponse.body; + if (!response.seatbid) { + return []; + } + const { seatbid, cur } = serverResponse.body; + const bidsReceived = seatbid + .map(seatbid => seatbid.bid) + .reduce((a, b) => a.concat(b), []); + + const currency = cur || DEFAULT_CURRENCY; + + return bidsReceived.map(bidReceived => { + const price = bidReceived.price; + let prBid = { + requestId: bidRequest.bidId, + cpm: price, + currency: currency, + width: bidReceived.w, + height: bidReceived.h, + creativeId: bidReceived.adid, + nurl: bidReceived.nurl, + + netRevenue: true, + ttl: DEFAULT_TTL, + + meta: { + advertiserDomains: bidReceived.adomain && bidReceived.adomain.length > 0 ? bidReceived.adomain : [], + } + }; + + if (bidReceived.adm.indexOf('{') === 0) { + prBid.mediaType = NATIVE; + prBid.native = interpretNativeAd(bidReceived, price, currency); + } else { + prBid.mediaType = BANNER; + prBid.ad = bidReceived.adm; + } + + return prBid; + }); +} + +function interpretNativeAd(bidReceived, price, currency) { + try { + const { adm } = bidReceived; + const { native } = JSON.parse(adm); + + const result = { + clickUrl: native.link.url, + }; + + native.assets.forEach(asset => { + const assetCode = NATIVE_ASSETS_IDS[asset.id]; + if (!assetCode) { + return; + } + if (assetCode === 'image' || assetCode === 'icon') { + result[assetCode] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + } else if (assetCode === 'title') { + result[assetCode] = asset.title.text; + } else { + result[assetCode] = asset.data.value; + } + }); + + result.impressionTrackers = _map(native.imptrackers, (tracker) => + replaceAuctionPrice(tracker, price, currency) + ); + + return result; + } catch (e) {} +} + +function replaceAuctionPrice(url, price, currency) { + if (!url) return; + + return url + .replace(/\${AUCTION_PRICE}/, price) + .replace(/\${AUCTION_CURRENCY}/, currency); +} + registerBidder(spec); diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md index aee51bca249..55a658cc25c 100644 --- a/modules/yandexBidAdapter.md +++ b/modules/yandexBidAdapter.md @@ -20,21 +20,58 @@ Yandex Bidder Adapter for Prebid.js. # Test Parameters -``` -var adUnits = [{ - code: 'banner-1', - mediaTypes: { - banner: { - sizes: [[240, 400], [300, 600]], - } +```javascript +var adUnits = [ + { // banner + code: 'banner-1', + mediaTypes: { + banner: { + sizes: [[240, 400], [300, 600]], + } + }, + bids: [ + { + bidder: 'yandex', + params: { + placementId: '346580-1' + }, + } + ], }, - bids: [{ - { - bidder: 'yandex', - params: { - placementId: '346580-1' + { // native + code: 'banner-2', + mediaTypes: { + native: { + title: { + required: true, + len: 25 + }, + image: { + required: true, + sizes: [300, 250], + }, + icon: { + sizes: [32, 32], + }, + body: { + len: 90 + }, + body2: { + len: 90 + }, + sponsoredBy: { + len: 25, + } }, - } - }] -}]; + }, + bids: [ + { + bidder: 'yandex', + params: { + placementId: '346580-1' + }, + } + ], + }, +]; ``` diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 31c6daae7f6..a16e4ec8d36 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -6,6 +6,7 @@ import CONSTANTS from '../src/constants.json'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storage = getStorageManager(); const yuktamediaAnalyticsVersion = 'v3.1.0'; @@ -34,7 +35,7 @@ const _pageInfo = { referer: referer, refererDomain: parseUrl(referer).host, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version }; function getParameterByName(param) { diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 1ed35792285..2f392ccdbd3 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -146,6 +146,15 @@ export const spec = { deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + // schain + if (validBidRequests[0].schain) { + payload.source = { + ext: { + schain: validBidRequests[0].schain + } + } + } + if (bidderRequest?.timeout) { payload.tmax = bidderRequest.timeout; } diff --git a/package-lock.json b/package-lock.json index 10239bd1959..276fa15775a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "7.40.0-pre", + "version": "7.44.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "7.36.0-pre", + "version": "7.42.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^4.0.0" + "live-connect-js": "^5.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16228,11 +16228,20 @@ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", "dev": true }, + "node_modules/live-connect-common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-1.0.0.tgz", + "integrity": "sha512-LBZsvykcGeVRYI1eqqXrrNZsoBdL2a8cpyrYPIiGAF/CpixbyRbvqGslaFw511lH294QB16J3fYYg21aYuaM2Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/live-connect-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", - "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-5.0.0.tgz", + "integrity": "sha512-Bv0wQQ+/1VU0/YczEpObbWtHbuXwaHGxwg1+Pe7ZlDgBLb334CrqSQvOL1uyZw3//zs+fSO94yYaQzjjkTd5OQ==", "dependencies": { + "live-connect-common": "^1.0.0", "tiny-hashes": "1.0.1" }, "engines": { @@ -24335,9 +24344,9 @@ "dev": true }, "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -37864,11 +37873,17 @@ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", "dev": true }, + "live-connect-common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-1.0.0.tgz", + "integrity": "sha512-LBZsvykcGeVRYI1eqqXrrNZsoBdL2a8cpyrYPIiGAF/CpixbyRbvqGslaFw511lH294QB16J3fYYg21aYuaM2Q==" + }, "live-connect-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", - "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-5.0.0.tgz", + "integrity": "sha512-Bv0wQQ+/1VU0/YczEpObbWtHbuXwaHGxwg1+Pe7ZlDgBLb334CrqSQvOL1uyZw3//zs+fSO94yYaQzjjkTd5OQ==", "requires": { + "live-connect-common": "^1.0.0", "tiny-hashes": "1.0.1" } }, @@ -44132,9 +44147,9 @@ "dev": true }, "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", diff --git a/package.json b/package.json index 65a93076782..c4efb745c42 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "prebid.js", - "version": "7.40.0-pre", + "version": "7.44.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { + "serve": "gulp serve", "test": "gulp test", "lint": "gulp lint" }, @@ -28,6 +29,7 @@ "prebid" ], "globalVarName": "pbjs", + "defineGlobal": true, "author": "the prebid.js contributors", "license": "Apache-2.0", "engines": { @@ -130,7 +132,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^4.0.0" + "live-connect-js": "^5.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index c1a08133e8e..4c8685db840 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -1,4 +1,3 @@ - let t = require('@babel/core').types; let prebid = require('../package.json'); const path = require('path'); @@ -28,10 +27,12 @@ function getNpmVersion(version) { module.exports = function(api, options) { const pbGlobal = options.globalVarName || prebid.globalVarName; + const defineGlobal = typeof (options.defineGlobal) !== 'undefined' ? options.defineGlobal : prebid.defineGlobal; const features = featureMap(options.disableFeatures); let replace = { '$prebid.version$': prebid.version, '$$PREBID_GLOBAL$$': pbGlobal, + '$$DEFINE_PREBID_GLOBAL$$': defineGlobal, '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`, '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/` }; @@ -42,6 +43,10 @@ module.exports = function(api, options) { const PREBID_ROOT = path.resolve(__dirname, '..'); + function relPath(from, toRelToProjectRoot) { + return path.relative(path.dirname(from), path.join(PREBID_ROOT, toRelToProjectRoot)); + } + function getModuleName(filename) { const modPath = path.parse(path.relative(PREBID_ROOT, filename)); if (modPath.ext.toLowerCase() !== '.js') { @@ -63,8 +68,14 @@ module.exports = function(api, options) { Program(path, state) { const modName = getModuleName(state.filename); if (modName != null) { - // append "registration" of module file to $$PREBID_GLOBAL$$.installedModules - path.node.body.push(...api.parse(`window.${pbGlobal}.installedModules.push('${modName}');`, {filename: state.filename}).program.body); + // append "registration" of module file to getGlobal().installedModules + let i = 0; + let registerName; + do { + registerName = `__r${i++}` + } while (path.scope.hasBinding(registerName)) + path.node.body.unshift(...api.parse(`import {registerModule as ${registerName}} from '${relPath(state.filename, 'src/prebidGlobal.js')}';`, {filename: state.filename}).program.body); + path.node.body.push(...api.parse(`${registerName}('${modName}');`, {filename: state.filename}).program.body); } }, StringLiteral(path) { @@ -72,7 +83,7 @@ module.exports = function(api, options) { if (path.node.value.includes(name)) { path.node.value = path.node.value.replace( new RegExp(escapeRegExp(name), 'g'), - replace[name] + replace[name].toString() ); } }); @@ -102,7 +113,7 @@ module.exports = function(api, options) { ); } else { path.replaceWith( - t.Identifier(replace[name]) + t.Identifier(replace[name].toString()) ); } } diff --git a/src/Renderer.js b/src/Renderer.js index 97e37084e89..cdee9c79e63 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -3,6 +3,9 @@ import { logError, logWarn, logMessage, deepAccess } from './utils.js'; import {find} from './polyfill.js'; +import {getGlobal} from './prebidGlobal.js'; + +const pbjsInstance = getGlobal(); const moduleCode = 'outstream'; /** @@ -129,7 +132,7 @@ export function executeRenderer(renderer, bid, doc) { } function isRendererPreferredFromAdUnit(adUnitCode) { - const adUnits = $$PREBID_GLOBAL$$.adUnits; + const adUnits = pbjsInstance.adUnits; const adUnit = find(adUnits, adUnit => { return adUnit.code === adUnitCode; }); diff --git a/src/adapterManager.js b/src/adapterManager.js index c9715fe0168..850488af8ae 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -338,13 +338,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); - bidRequests.forEach(bidRequest => { - config.runWithBidder(bidRequest.bidderCode, () => { - const fledgeEnabledFromConfig = config.getConfig('fledgeEnabled'); - bidRequest['fledgeEnabled'] = navigator.runAdAuction && fledgeEnabledFromConfig - }); - }); - return bidRequests; }, 'makeBidRequests'); @@ -459,7 +452,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request function getSupportedMediaTypes(bidderCode) { let supportedMediaTypes = []; - if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); + if (FEATURES.VIDEO && includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); if (FEATURES.NATIVE && includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); return supportedMediaTypes; } @@ -471,7 +464,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (typeof bidAdapter.callBids === 'function') { _bidderRegistry[bidderCode] = bidAdapter; - if (includes(supportedMediaTypes, 'video')) { + if (FEATURES.VIDEO && includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); } if (FEATURES.NATIVE && includes(supportedMediaTypes, 'native')) { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 55c84e57062..d6a43e409e3 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -1,20 +1,32 @@ import Adapter from '../adapter.js'; import adapterManager from '../adapterManager.js'; -import { config } from '../config.js'; -import { createBid } from '../bidfactory.js'; -import { userSync } from '../userSync.js'; -import { nativeBidIsValid } from '../native.js'; -import { isValidVideoBid } from '../video.js'; +import {config} from '../config.js'; +import {createBid} from '../bidfactory.js'; +import {userSync} from '../userSync.js'; +import {nativeBidIsValid} from '../native.js'; +import {isValidVideoBid} from '../video.js'; import CONSTANTS from '../constants.json'; import * as events from '../events.js'; import {includes} from '../polyfill.js'; -import { ajax } from '../ajax.js'; -import { logWarn, logInfo, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; -import { ADPOD } from '../mediaTypes.js'; -import { getHook, hook } from '../hook.js'; -import { getCoreStorageManager } from '../storageManager.js'; +import {ajax} from '../ajax.js'; +import { + deepAccess, + delayExecution, + flatten, + isArray, + isPlainObject, + logError, + logWarn, + parseQueryStringParameters, + parseSizesInput, + timestamp, + uniques +} from '../utils.js'; +import {ADPOD} from '../mediaTypes.js'; +import {getHook, hook} from '../hook.js'; +import {getCoreStorageManager} from '../storageManager.js'; import {auctionManager} from '../auctionManager.js'; -import { bidderSettings } from '../bidderSettings.js'; +import {bidderSettings} from '../bidderSettings.js'; import {useMetrics} from '../utils/perfMetrics.js'; export const storage = getCoreStorageManager('bidderFactory'); @@ -247,7 +259,9 @@ export function newBidder(spec) { fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest, fledgeAuctionConfig); + addComponentAuction(bidRequest.adUnitCode, fledgeAuctionConfig.config); + } else { + logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); } }); }, @@ -467,56 +481,60 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') -export const addComponentAuction = hook('sync', (_bidRequest, fledgeAuctionConfig) => { - logInfo(`bidderFactory.addComponentAuction`, fledgeAuctionConfig); -}, 'addComponentAuction') +export const addComponentAuction = hook('sync', (adUnitCode, fledgeAuctionConfig) => { +}, 'addComponentAuction'); export function preloadBidderMappingFile(fn, adUnits) { - if (!config.getConfig('adpod.brandCategoryExclusion')) { - return fn.call(this, adUnits); - } - let adPodBidders = adUnits - .filter((adUnit) => deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) - .map((adUnit) => adUnit.bids.map((bid) => bid.bidder)) - .reduce(flatten, []) - .filter(uniques); - - adPodBidders.forEach(bidder => { - let bidderSpec = adapterManager.getBidAdapter(bidder); - if (bidderSpec.getSpec().getMappingFileInfo) { - let info = bidderSpec.getSpec().getMappingFileInfo(); - let refreshInDays = (info.refreshInDays) ? info.refreshInDays : DEFAULT_REFRESHIN_DAYS; - let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getSpec().code; - let mappingData = storage.getDataFromLocalStorage(key); - try { - mappingData = mappingData ? JSON.parse(mappingData) : undefined; - if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { - ajax(info.url, - { - success: (response) => { - try { - response = JSON.parse(response); - let mapping = { - lastUpdated: timestamp(), - mapping: response.mapping + if (FEATURES.VIDEO) { + if (!config.getConfig('adpod.brandCategoryExclusion')) { + return fn.call(this, adUnits); + } + + let adPodBidders = adUnits + .filter((adUnit) => deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) + .map((adUnit) => adUnit.bids.map((bid) => bid.bidder)) + .reduce(flatten, []) + .filter(uniques); + + adPodBidders.forEach(bidder => { + let bidderSpec = adapterManager.getBidAdapter(bidder); + if (bidderSpec.getSpec().getMappingFileInfo) { + let info = bidderSpec.getSpec().getMappingFileInfo(); + let refreshInDays = (info.refreshInDays) ? info.refreshInDays : DEFAULT_REFRESHIN_DAYS; + let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getSpec().code; + let mappingData = storage.getDataFromLocalStorage(key); + try { + mappingData = mappingData ? JSON.parse(mappingData) : undefined; + if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { + ajax(info.url, + { + success: (response) => { + try { + response = JSON.parse(response); + let mapping = { + lastUpdated: timestamp(), + mapping: response.mapping + } + storage.setDataInLocalStorage(key, JSON.stringify(mapping)); + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } - storage.setDataInLocalStorage(key, JSON.stringify(mapping)); - } catch (error) { - logError(`Failed to parse ${bidder} bidder translation mapping file`); + }, + error: () => { + logError(`Failed to load ${bidder} bidder translation file`) } }, - error: () => { - logError(`Failed to load ${bidder} bidder translation file`) - } - }, - ); + ); + } + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } - } catch (error) { - logError(`Failed to parse ${bidder} bidder translation mapping file`); } - } - }); - fn.call(this, adUnits); + }); + fn.call(this, adUnits); + } else { + return fn.call(this, adUnits) + } } getHook('checkAdUnitSetup').before(preloadBidderMappingFile); @@ -599,7 +617,7 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { logError(errorMessage('Native bid missing some required properties.')); return false; } - if (bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { + if (FEATURES.VIDEO && bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); return false; } diff --git a/src/auction.js b/src/auction.js index 6dd0432712f..60892e5d7a2 100644 --- a/src/auction.js +++ b/src/auction.js @@ -94,6 +94,7 @@ import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {createBid} from './bidfactory.js'; import {adjustCpm} from './utils/cpm.js'; +import {getGlobal} from './prebidGlobal.js'; const { syncUsers } = userSync; @@ -111,6 +112,8 @@ const outstandingRequests = {}; const sourceInfo = {}; const queuedCalls = []; +const pbjsInstance = getGlobal(); + /** * Clear global state for tests */ @@ -216,7 +219,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a const bids = _bidsReceived .filter(bind.call(adUnitsFilter, this, adUnitCodes)) .reduce(groupByPlacement, {}); - _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut, _auctionId]); + _callback.apply(pbjsInstance, [bids, timedOut, _auctionId]); _callback = null; } } catch (e) { @@ -473,7 +476,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM handleBidResponse(adUnitCode, bid, (done) => { let bidResponse = getPreparedBidForAuction(bid); - if (bidResponse.mediaType === VIDEO) { + if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { tryAddVideoBid(auctionInstance, bidResponse, done); } else { if (FEATURES.NATIVE && bidResponse.native != null && typeof bidResponse.native === 'object') { @@ -620,7 +623,7 @@ const addLegacyFieldsIfNeeded = (bidResponse) => { } } -const storeInCache = (batch) => { +const _storeInCache = (batch) => { store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { cacheIds.forEach((cacheId, i) => { const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; @@ -647,6 +650,8 @@ const storeInCache = (batch) => { }); }; +const storeInCache = FEATURES.VIDEO ? _storeInCache : () => {}; + let batchSize, batchTimeout; config.getConfig('cache', (cacheConfig) => { batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 @@ -782,7 +787,7 @@ function setupBidTargeting(bidObject) { */ export function getMediaTypeGranularity(mediaType, mediaTypes, mediaTypePriceGranularity) { if (mediaType && mediaTypePriceGranularity) { - if (mediaType === VIDEO) { + if (FEATURES.VIDEO && mediaType === VIDEO) { const context = deepAccess(mediaTypes, `${VIDEO}.context`, 'instream'); if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) { return mediaTypePriceGranularity[`${VIDEO}-${context}`]; @@ -835,7 +840,7 @@ export const getPriceByGranularity = (granularity) => { */ export const getAdvertiserDomain = () => { return (bid) => { - return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? bid.meta.advertiserDomains[0] : ''; + return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? [bid.meta.advertiserDomains].flat()[0] : ''; } } @@ -891,7 +896,7 @@ export function getStandardBidderSettings(mediaType, bidderCode) { standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); } - if (mediaType === 'video') { + if (FEATURES.VIDEO && mediaType === 'video') { const adserverTargeting = standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].slice(); standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = adserverTargeting; diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index fc50a057378..f812d8435d9 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -3,8 +3,9 @@ import {getRefererInfo, parseDomain} from '../refererDetection.js'; import {findRootDomain} from './rootDomain.js'; import {deepSetValue, getDefinedParams, getDNT, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; import {config} from '../config.js'; -import {getHighEntropySUA, getLowEntropySUA} from '../../libraries/fpd/sua.js'; +import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; import {GreedyPromise} from '../utils/promise.js'; +import {CLIENT_SECTIONS, clientSectionChecker, hasSection} from './oneClient.js'; export const dep = { getRefererInfo, @@ -15,6 +16,8 @@ export const dep = { getLowEntropySUA, }; +const oneClient = clientSectionChecker('FPD') + /** * Enrich an ortb2 object with first party data. * @param {Promise[{}]} fpd: a promise to an ortb2 object. @@ -23,8 +26,10 @@ export const dep = { export const enrichFPD = hook('sync', (fpd) => { return GreedyPromise.all([fpd, getSUA().catch(() => null)]) .then(([ortb2, sua]) => { + const ri = dep.getRefererInfo(); + mergeLegacySetConfigs(ortb2); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { - const data = getEnrichments(); + const data = getEnrichments(ortb2, ri); if (data && Object.keys(data).length > 0) { ortb2[section] = mergeDeep({}, data, ortb2[section]); } @@ -32,10 +37,28 @@ export const enrichFPD = hook('sync', (fpd) => { if (sua) { deepSetValue(ortb2, 'device.sua', Object.assign({}, sua, ortb2.device.sua)); } + ortb2 = oneClient(ortb2); + for (let section of CLIENT_SECTIONS) { + if (hasSection(ortb2, section)) { + ortb2[section] = mergeDeep({}, clientEnrichment(ortb2, ri), ortb2[section]); + break; + } + } return ortb2; }); }); +function mergeLegacySetConfigs(ortb2) { + // merge in values from "legacy" setConfig({app, site, device}) + // TODO: deprecate these eventually + ['app', 'site', 'device'].forEach(prop => { + const cfg = config.getConfig(prop); + if (cfg != null) { + ortb2[prop] = mergeDeep({}, cfg, ortb2[prop]); + } + }) +} + function winFallback(fn) { try { return fn(dep.getWindowTop()); @@ -46,25 +69,24 @@ function winFallback(fn) { function getSUA() { const hints = config.getConfig('firstPartyData.uaHints'); - return Array.isArray(hints) && hints.length === 0 + return !Array.isArray(hints) || hints.length === 0 ? GreedyPromise.resolve(dep.getLowEntropySUA()) : dep.getHighEntropySUA(hints); } +function removeUndef(obj) { + return getDefinedParams(obj, Object.keys(obj)) +} + const ENRICHMENTS = { - site() { - const ri = dep.getRefererInfo(); - const domain = parseDomain(ri.page, {noLeadingWww: true}); - const keywords = winFallback((win) => win.document.querySelector('meta[name=\'keywords\']')) - ?.content?.replace?.(/\s/g, ''); - return (site => getDefinedParams(site, Object.keys(site)))({ + site(ortb2, ri) { + if (CLIENT_SECTIONS.filter(p => p !== 'site').some(hasSection.bind(null, ortb2))) { + // do not enrich site if dooh or app are set + return; + } + return removeUndef({ page: ri.page, ref: ri.ref, - domain, - keywords, - publisher: { - domain: dep.findRootDomain(domain) - } }); }, device() { @@ -92,3 +114,18 @@ const ENRICHMENTS = { return regs; } }; + +// Enrichment of properties common across dooh, app and site - will be dropped into whatever +// section is appropriate +function clientEnrichment(ortb2, ri) { + const domain = parseDomain(ri.page, {noLeadingWww: true}); + const keywords = winFallback((win) => win.document.querySelector('meta[name=\'keywords\']')) + ?.content?.replace?.(/\s/g, ''); + return removeUndef({ + domain, + keywords, + publisher: removeUndef({ + domain: dep.findRootDomain(domain) + }) + }) +} diff --git a/src/fpd/oneClient.js b/src/fpd/oneClient.js new file mode 100644 index 00000000000..67f53c73bd8 --- /dev/null +++ b/src/fpd/oneClient.js @@ -0,0 +1,26 @@ +import {logWarn} from '../utils.js'; + +// mutually exclusive ORTB sections in order of priority - 'dooh' beats 'app' & 'site' and 'app' beats 'site'; +// if one is set, the others will be removed +export const CLIENT_SECTIONS = ['dooh', 'app', 'site'] + +export function clientSectionChecker(logPrefix) { + return function onlyOneClientSection(ortb2) { + CLIENT_SECTIONS.reduce((found, section) => { + if (hasSection(ortb2, section)) { + if (found != null) { + logWarn(`${logPrefix} specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortb2[section]; + } else { + found = section; + } + } + return found; + }, null); + return ortb2; + } +} + +export function hasSection(ortb2, section) { + return ortb2[section] != null && Object.keys(ortb2[section]).length > 0 +} diff --git a/libraries/fpd/sua.js b/src/fpd/sua.js similarity index 96% rename from libraries/fpd/sua.js rename to src/fpd/sua.js index b6a46763514..30b2be4c13b 100644 --- a/libraries/fpd/sua.js +++ b/src/fpd/sua.js @@ -1,5 +1,5 @@ -import {isEmptyStr, isStr, isEmpty} from '../../src/utils.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {isEmptyStr, isStr, isEmpty} from '../utils.js'; +import {GreedyPromise} from '../utils/promise.js'; export const SUA_SOURCE_UNKNOWN = 0; export const SUA_SOURCE_LOW_ENTROPY = 1; diff --git a/src/native.js b/src/native.js index 25f8c38cb30..c4413a1a6de 100644 --- a/src/native.js +++ b/src/native.js @@ -9,8 +9,8 @@ import { isNumber, isPlainObject, logError, - triggerPixel, - pick + pick, + triggerPixel } from './utils.js'; import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; @@ -385,16 +385,19 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { return keyValues; } -function assetsMessage(data, adObject, keys) { +function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { const message = { message: 'assetResponse', adId: data.adId, }; + const adUnit = index.getAdUnit(adObject); let nativeResp = adObject.native; if (adObject.native.ortb) { message.ortb = adObject.native.ortb; + } else if (adUnit.mediaTypes?.native?.ortb) { + message.ortb = toOrtbNativeResponse(adObject.native, adUnit.nativeOrtbRequest); } message.assets = []; @@ -698,7 +701,7 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) { } Object.keys(legacyResponse).filter(key => !!legacyResponse[key]).forEach(key => { - const value = legacyResponse[key]; + const value = getAssetValue(legacyResponse[key]); switch (key) { // process titles case 'title': diff --git a/src/prebid.js b/src/prebid.js index 9ad72409990..5070b6b42a4 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -52,7 +52,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; -const $$PREBID_GLOBAL$$ = getGlobal(); +const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; /* private variables */ @@ -67,22 +67,22 @@ const eventValidators = { loadSession(); /* Public vars */ -$$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; +pbjsInstance.bidderSettings = pbjsInstance.bidderSettings || {}; // let the world know we are loaded -$$PREBID_GLOBAL$$.libLoaded = true; +pbjsInstance.libLoaded = true; // version auto generated from build -$$PREBID_GLOBAL$$.version = 'v$prebid.version$'; +pbjsInstance.version = 'v$prebid.version$'; logInfo('Prebid.js v$prebid.version$ loaded'); -$$PREBID_GLOBAL$$.installedModules = $$PREBID_GLOBAL$$.installedModules || []; +pbjsInstance.installedModules = pbjsInstance.installedModules || []; // create adUnit array -$$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; +pbjsInstance.adUnits = pbjsInstance.adUnits || []; // Allow publishers who enable user sync override to trigger their sync -$$PREBID_GLOBAL$$.triggerUserSyncs = triggerUserSyncs; +pbjsInstance.triggerUserSyncs = triggerUserSyncs; function checkDefinedPlacement(id) { var adUnitCodes = auctionManager.getBidsRequested().map(bidSet => bidSet.bids.map(bid => bid.adUnitCode)) @@ -225,7 +225,6 @@ function validateAdUnit(adUnit) { export const adUnitSetupChecks = { validateAdUnit, validateBannerMediaType, - validateVideoMediaType, validateSizes }; @@ -233,6 +232,10 @@ if (FEATURES.NATIVE) { Object.assign(adUnitSetupChecks, {validateNativeMediaType}); } +if (FEATURES.VIDEO) { + Object.assign(adUnitSetupChecks, { validateVideoMediaType }); +} + export const checkAdUnitSetup = hook('sync', function (adUnits) { const validatedAdUnits = []; @@ -248,7 +251,7 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { if (mediaTypes.banner.hasOwnProperty('pos')) validatedBanner = validateAdUnitPos(validatedBanner, 'banner'); } - if (mediaTypes.video) { + if (FEATURES.VIDEO && mediaTypes.video) { validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } @@ -277,12 +280,12 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr * @return {Array} returnObj return bids array */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { +pbjsInstance.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr', arguments); // call to retrieve bids array if (adunitCode) { - var res = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode(adunitCode); + var res = pbjsInstance.getAdserverTargetingForAdUnitCode(adunitCode); return transformAdServerTargetingObj(res); } else { logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); @@ -295,7 +298,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode * @returns {Object} returnObj return bid */ -$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { +pbjsInstance.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { if (adunitCode) { const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) .filter(isBidUsable) @@ -312,8 +315,8 @@ $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCod * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { - return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; +pbjsInstance.getAdserverTargetingForAdUnitCode = function (adUnitCode) { + return pbjsInstance.getAdserverTargeting(adUnitCode)[adUnitCode]; }; /** @@ -322,7 +325,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { * @alias module:pbjs.getAdserverTargeting */ -$$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { +pbjsInstance.getAdserverTargeting = function (adUnitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); return targeting.getAllTargeting(adUnitCode); }; @@ -341,7 +344,7 @@ function getConsentMetadata() { } } -$$PREBID_GLOBAL$$.getConsentMetadata = function () { +pbjsInstance.getConsentMetadata = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getConsentMetadata'); return getConsentMetadata(); }; @@ -372,7 +375,7 @@ function getBids(type) { * @return {Object} map | object that contains the bidRequests */ -$$PREBID_GLOBAL$$.getNoBids = function () { +pbjsInstance.getNoBids = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getNoBids', arguments); return getBids('getNoBids'); }; @@ -384,7 +387,7 @@ $$PREBID_GLOBAL$$.getNoBids = function () { * @return {Object} bidResponse object */ -$$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { +pbjsInstance.getNoBidsForAdUnitCode = function (adUnitCode) { const bids = auctionManager.getNoBids().filter(bid => bid.adUnitCode === adUnitCode); return { bids }; }; @@ -395,7 +398,7 @@ $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { * @return {Object} map | object that contains the bidResponses */ -$$PREBID_GLOBAL$$.getBidResponses = function () { +pbjsInstance.getBidResponses = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments); return getBids('getBidsReceived'); }; @@ -407,7 +410,7 @@ $$PREBID_GLOBAL$$.getBidResponses = function () { * @return {Object} bidResponse object */ -$$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { +pbjsInstance.getBidResponsesForAdUnitCode = function (adUnitCode) { const bids = auctionManager.getBidsReceived().filter(bid => bid.adUnitCode === adUnitCode); return { bids }; }; @@ -418,7 +421,7 @@ $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { * @param {function(object)} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; * @alias module:pbjs.setTargetingForGPTAsync */ -$$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { +pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); if (!isGptPubadsDefined()) { logError('window.googletag is not defined on the page'); @@ -451,7 +454,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { +pbjsInstance.setTargetingForAst = function (adUnitCodes) { logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { logError('window.apntag is not defined on the page'); @@ -484,7 +487,7 @@ function reinjectNodeIfRemoved(node, doc, tagName) { * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -$$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { +pbjsInstance.renderAd = hook('async', function (doc, id, options) { logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); logMessage('Calling renderAd with adId :' + id); @@ -530,12 +533,14 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { const {height, width, ad, mediaType, adUrl, renderer} = bid; // video module - const adUnitCode = bid.adUnitCode; - const adUnit = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnit.code === adUnitCode); - const videoModule = $$PREBID_GLOBAL$$.videoModule; - if (adUnit.video && videoModule) { - videoModule.renderBid(adUnit.video.divId, bid); - return; + if (FEATURES.VIDEO) { + const adUnitCode = bid.adUnitCode; + const adUnit = pbjsInstance.adUnits.filter(adUnit => adUnit.code === adUnitCode); + const videoModule = pbjsInstance.videoModule; + if (adUnit.video && videoModule) { + videoModule.renderBid(adUnit.video.divId, bid); + return; + } } if (!doc) { @@ -589,11 +594,11 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { * @param {string| Array} adUnitCode the adUnitCode(s) to remove * @alias module:pbjs.removeAdUnit */ -$$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { +pbjsInstance.removeAdUnit = function (adUnitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); if (!adUnitCode) { - $$PREBID_GLOBAL$$.adUnits = []; + pbjsInstance.adUnits = []; return; } @@ -606,9 +611,9 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { } adUnitCodes.forEach((adUnitCode) => { - for (let i = $$PREBID_GLOBAL$$.adUnits.length - 1; i >= 0; i--) { - if ($$PREBID_GLOBAL$$.adUnits[i].code === adUnitCode) { - $$PREBID_GLOBAL$$.adUnits.splice(i, 1); + for (let i = pbjsInstance.adUnits.length - 1; i >= 0; i--) { + if (pbjsInstance.adUnits[i].code === adUnitCode) { + pbjsInstance.adUnits.splice(i, 1); } } }); @@ -624,7 +629,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @param {String} requestOptions.auctionId * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = (function() { +pbjsInstance.requestBids = (function() { const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2, metrics, defer } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); @@ -653,7 +658,7 @@ $$PREBID_GLOBAL$$.requestBids = (function() { // if the request does not specify adUnits, clone the global adUnit array; // otherwise, if the caller goes on to use addAdUnits/removeAdUnits, any asynchronous logic // in any hook might see their effects. - let adUnits = req.adUnits || $$PREBID_GLOBAL$$.adUnits; + let adUnits = req.adUnits || pbjsInstance.adUnits; req.adUnits = (isArray(adUnits) ? adUnits.slice() : [adUnits]); req.metrics = newMetrics(); @@ -761,7 +766,7 @@ export function executeCallbacks(fn, reqBidsConfigObj) { } // This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not -$$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); +pbjsInstance.requestBids.before(executeCallbacks, 49); /** * @@ -769,9 +774,9 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); * @param {Array|Object} adUnitArr Array of adUnits or single adUnit Object. * @alias module:pbjs.addAdUnits */ -$$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { +pbjsInstance.addAdUnits = function (adUnitArr) { logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, isArray(adUnitArr) ? adUnitArr : [adUnitArr]); + pbjsInstance.adUnits.push.apply(pbjsInstance.adUnits, isArray(adUnitArr) ? adUnitArr : [adUnitArr]); // emit event events.emit(ADD_AD_UNITS); }; @@ -792,7 +797,7 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { * * Currently `bidWon` is the only event that accepts an `id` parameter. */ -$$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { +pbjsInstance.onEvent = function (event, handler, id) { logInfo('Invoking $$PREBID_GLOBAL$$.onEvent', arguments); if (!isFn(handler)) { logError('The event handler provided is not a function and was not set on event "' + event + '".'); @@ -813,7 +818,7 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { * @param {string} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) * @alias module:pbjs.offEvent */ -$$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { +pbjsInstance.offEvent = function (event, handler, id) { logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); if (id && !eventValidators[event].call(null, id)) { return; @@ -827,7 +832,7 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { * * @alias module:pbjs.getEvents */ -$$PREBID_GLOBAL$$.getEvents = function () { +pbjsInstance.getEvents = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getEvents'); return events.getEvents(); }; @@ -838,7 +843,7 @@ $$PREBID_GLOBAL$$.getEvents = function () { * @param {string} bidderCode [description] * @alias module:pbjs.registerBidAdapter */ -$$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { +pbjsInstance.registerBidAdapter = function (bidderAdaptor, bidderCode) { logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); try { adapterManager.registerBidAdapter(bidderAdaptor(), bidderCode); @@ -852,7 +857,7 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { * @param {Object} options [description] * @alias module:pbjs.registerAnalyticsAdapter */ -$$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { +pbjsInstance.registerAnalyticsAdapter = function (options) { logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); try { adapterManager.registerAnalyticsAdapter(options); @@ -867,7 +872,7 @@ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { * @alias module:pbjs.createBid * @return {Object} bidResponse [description] */ -$$PREBID_GLOBAL$$.createBid = function (statusCode) { +pbjsInstance.createBid = function (statusCode) { logInfo('Invoking $$PREBID_GLOBAL$$.createBid', arguments); return createBid(statusCode); }; @@ -899,14 +904,14 @@ const enableAnalyticsCb = hook('async', function (config) { } }, 'enableAnalyticsCb'); -$$PREBID_GLOBAL$$.enableAnalytics = function (config) { +pbjsInstance.enableAnalytics = function (config) { enableAnalyticsCallbacks.push(enableAnalyticsCb.bind(this, config)); }; /** * @alias module:pbjs.aliasBidder */ -$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { +pbjsInstance.aliasBidder = function (bidderCode, alias, options) { logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { adapterManager.aliasBidAdapter(bidderCode, alias, options); @@ -918,9 +923,9 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { /** * @alias module:pbjs.aliasRegistry */ -$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +pbjsInstance.aliasRegistry = adapterManager.aliasRegistry; config.getConfig('aliasRegistry', config => { - if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; + if (config.aliasRegistry === 'private') delete pbjsInstance.aliasRegistry; }); /** @@ -962,7 +967,7 @@ config.getConfig('aliasRegistry', config => { * Get all of the bids that have been rendered. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html). * @return {Array<AdapterBidResponse>} A list of bids that have been rendered. */ -$$PREBID_GLOBAL$$.getAllWinningBids = function () { +pbjsInstance.getAllWinningBids = function () { return auctionManager.getAllWinningBids(); }; @@ -970,7 +975,7 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () { * Get all of the bids that have won their respective auctions. * @return {Array<AdapterBidResponse>} A list of bids that have won their respective auctions. */ -$$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () { +pbjsInstance.getAllPrebidWinningBids = function () { return auctionManager.getBidsReceived() .filter(bid => bid.status === CONSTANTS.BID_STATUS.BID_TARGETING_SET); }; @@ -982,46 +987,48 @@ $$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () { * @alias module:pbjs.getHighestCpmBids * @return {Array} array containing highest cpm bid object(s) */ -$$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { +pbjsInstance.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; -/** - * Mark the winning bid as used, should only be used in conjunction with video - * @typedef {Object} MarkBidRequest - * @property {string} adUnitCode The ad unit code - * @property {string} adId The id representing the ad we want to mark - * - * @alias module:pbjs.markWinningBidAsUsed - */ -$$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { - let bids = []; - - if (markBidRequest.adUnitCode && markBidRequest.adId) { - bids = auctionManager.getBidsReceived() - .filter(bid => bid.adId === markBidRequest.adId && bid.adUnitCode === markBidRequest.adUnitCode); - } else if (markBidRequest.adUnitCode) { - bids = targeting.getWinningBids(markBidRequest.adUnitCode); - } else if (markBidRequest.adId) { - bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); - } else { - logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); - } +if (FEATURES.VIDEO) { + /** + * Mark the winning bid as used, should only be used in conjunction with video + * @typedef {Object} MarkBidRequest + * @property {string} adUnitCode The ad unit code + * @property {string} adId The id representing the ad we want to mark + * + * @alias module:pbjs.markWinningBidAsUsed + */ + pbjsInstance.markWinningBidAsUsed = function (markBidRequest) { + let bids = []; + + if (markBidRequest.adUnitCode && markBidRequest.adId) { + bids = auctionManager.getBidsReceived() + .filter(bid => bid.adId === markBidRequest.adId && bid.adUnitCode === markBidRequest.adUnitCode); + } else if (markBidRequest.adUnitCode) { + bids = targeting.getWinningBids(markBidRequest.adUnitCode); + } else if (markBidRequest.adId) { + bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); + } else { + logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); + } - if (bids.length > 0) { - bids[0].status = CONSTANTS.BID_STATUS.RENDERED; + if (bids.length > 0) { + bids[0].status = CONSTANTS.BID_STATUS.RENDERED; + } } -}; +} /** * Get Prebid config options * @param {Object} options * @alias module:pbjs.getConfig */ -$$PREBID_GLOBAL$$.getConfig = config.getAnyConfig; -$$PREBID_GLOBAL$$.readConfig = config.readAnyConfig; -$$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig; -$$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; +pbjsInstance.getConfig = config.getAnyConfig; +pbjsInstance.readConfig = config.readAnyConfig; +pbjsInstance.mergeConfig = config.mergeConfig; +pbjsInstance.mergeBidderConfig = config.mergeBidderConfig; /** * Set Prebid config options. @@ -1029,10 +1036,10 @@ $$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; * * @param {Object} options Global Prebid configuration object. Must be JSON - no JavaScript functions are allowed. */ -$$PREBID_GLOBAL$$.setConfig = config.setConfig; -$$PREBID_GLOBAL$$.setBidderConfig = config.setBidderConfig; +pbjsInstance.setConfig = config.setConfig; +pbjsInstance.setBidderConfig = config.setBidderConfig; -$$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); +pbjsInstance.que.push(() => listenMessagesFromCreative()); /** * This queue lets users load Prebid asynchronously, but run functions the same way regardless of whether it gets loaded @@ -1054,7 +1061,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function (command) { +pbjsInstance.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -1066,7 +1073,7 @@ $$PREBID_GLOBAL$$.cmd.push = function (command) { } }; -$$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; +pbjsInstance.que.push = pbjsInstance.cmd.push; function processQueue(queue) { queue.forEach(function (cmd) { @@ -1084,10 +1091,10 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function () { +pbjsInstance.processQueue = function () { hook.ready(); - processQueue($$PREBID_GLOBAL$$.que); - processQueue($$PREBID_GLOBAL$$.cmd); + processQueue(pbjsInstance.que); + processQueue(pbjsInstance.cmd); }; -export default $$PREBID_GLOBAL$$; +export default pbjsInstance; diff --git a/src/prebidGlobal.js b/src/prebidGlobal.js index 5eed4b3670f..4cbc3e10ad1 100644 --- a/src/prebidGlobal.js +++ b/src/prebidGlobal.js @@ -1,13 +1,21 @@ // if $$PREBID_GLOBAL$$ already exists in global document scope, use it, if not, create the object // global defination should happen BEFORE imports to avoid global undefined errors. -window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); -window.$$PREBID_GLOBAL$$.cmd = window.$$PREBID_GLOBAL$$.cmd || []; -window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; +/* global $$DEFINE_PREBID_GLOBAL$$ */ +const scope = !$$DEFINE_PREBID_GLOBAL$$ ? {} : window; +const global = scope.$$PREBID_GLOBAL$$ = scope.$$PREBID_GLOBAL$$ || {}; +global.cmd = global.cmd || []; +global.que = global.que || []; // create a pbjs global pointer -window._pbjsGlobals = window._pbjsGlobals || []; -window._pbjsGlobals.push('$$PREBID_GLOBAL$$'); +if (scope === window) { + scope._pbjsGlobals = scope._pbjsGlobals || []; + scope._pbjsGlobals.push('$$PREBID_GLOBAL$$'); +} export function getGlobal() { - return window.$$PREBID_GLOBAL$$; + return global; +} + +export function registerModule(name) { + global.installedModules.push(name); } diff --git a/src/storageManager.js b/src/storageManager.js index eaf35603c60..b8906b131e4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -7,6 +7,9 @@ const moduleTypeWhiteList = ['core', 'prebid-module']; export let storageCallbacks = []; +export const STORAGE_TYPE_LOCALSTORAGE = 'html5'; +export const STORAGE_TYPE_COOKIES = 'cookie'; + /** * Storage options * @typedef {Object} storageOptions @@ -26,20 +29,22 @@ export let storageCallbacks = []; * @param {storageOptions} options */ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { - function isBidderAllowed() { + function isBidderAllowed(storageType) { if (bidderCode == null) { return true; } const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); - return storageAllowed == null ? false : storageAllowed; + if (!storageAllowed || storageAllowed === true) return !!storageAllowed; + if (Array.isArray(storageAllowed)) return storageAllowed.some((e) => e === storageType); + return storageAllowed === storageType; } if (moduleTypeWhiteList.includes(moduleType)) { gvlid = gvlid || VENDORLESS_GVLID; } - function isValid(cb) { - if (!isBidderAllowed()) { + function isValid(cb, storageType) { + if (!isBidderAllowed(storageType)) { logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); const result = {valid: false}; return cb(result); @@ -63,6 +68,17 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } } + function schedule(operation, storageType, done) { + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(operation, storageType); + done(result); + }); + } else { + return isValid(operation, storageType); + } + } + /** * @param {string} key * @param {string} value @@ -83,14 +99,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}${secure}`; } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); }; /** @@ -105,14 +114,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return null; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); }; /** @@ -133,14 +135,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -153,14 +148,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); } /** @@ -173,14 +161,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = window.localStorage.setItem(key, value); } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -194,14 +175,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return null; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -213,14 +187,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = window.localStorage.removeItem(key); } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -237,14 +204,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -273,14 +233,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); } return { diff --git a/src/utils.js b/src/utils.js index 8df45bab9d2..61cd136b850 100644 --- a/src/utils.js +++ b/src/utils.js @@ -3,6 +3,7 @@ import clone from 'just-clone'; import {find, includes} from './polyfill.js'; import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; +import {getGlobal} from './prebidGlobal.js'; export { default as deepAccess } from 'dlv/index.js'; export { dset as deepSetValue } from 'dset'; @@ -21,6 +22,8 @@ let consoleErrorExists = Boolean(consoleExists && window.console.error); let eventEmitter; +const pbjsInstance = getGlobal(); + export function _setEventEmitter(emitFn) { // called from events.js - this hoop is to avoid circular imports eventEmitter = emitFn; @@ -707,7 +710,7 @@ export function getKeyByValue(obj, value) { } } -export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { +export function getBidderCodes(adUnits = pbjsInstance.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques); @@ -904,7 +907,7 @@ export function isValidMediaTypes(mediaTypes) { return false; } - if (mediaTypes.video && mediaTypes.video.context) { + if (FEATURES.VIDEO && mediaTypes.video && mediaTypes.video.context) { return includes(SUPPORTED_STREAM_TYPES, mediaTypes.video.context); } diff --git a/src/utils/promise.js b/src/utils/promise.js index 69e40791ab1..0cf0a47eb8e 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -1,15 +1,12 @@ -import {getGlobal} from '../prebidGlobal.js'; - const SUCCESS = 0; const FAIL = 1; /** * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). */ -export class GreedyPromise extends (getGlobal().Promise || Promise) { +export class GreedyPromise { #result; #callbacks; - #parent = null; /** * Convenience wrapper for setTimeout; takes care of returning an already fulfilled GreedyPromise when the delay is zero. @@ -24,53 +21,33 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { } constructor(resolver) { + if (typeof resolver !== 'function') { + throw new Error('resolver not a function'); + } const result = []; const callbacks = []; - function handler(type, resolveFn) { + let [resolve, reject] = [SUCCESS, FAIL].map((type) => { return function (value) { - if (!result.length) { + if (type === SUCCESS && typeof value?.then === 'function') { + value.then(resolve, reject); + } else if (!result.length) { result.push(type, value); while (callbacks.length) callbacks.shift()(); - resolveFn(value); } } + }); + try { + resolver(resolve, reject); + } catch (e) { + reject(e); } - super( - typeof resolver !== 'function' - ? resolver // let super throw an error - : (resolve, reject) => { - const rejectHandler = handler(FAIL, reject); - const resolveHandler = (() => { - const done = handler(SUCCESS, resolve); - return value => - typeof value?.then === 'function' ? value.then(done, rejectHandler) : done(value); - })(); - try { - resolver(resolveHandler, rejectHandler); - } catch (e) { - rejectHandler(e); - } - } - ); this.#result = result; this.#callbacks = callbacks; } + then(onSuccess, onError) { - if (typeof onError === 'function') { - // if an error handler is provided, attach a dummy error handler to super, - // and do the same for all promises without an error handler that precede this one in a chain. - // This is to avoid unhandled rejection events / warnings for errors that were, in fact, handled; - // since we are not using super's callback mechanisms we need to make it aware of this separately. - let node = this; - while (node) { - super.then.call(node, null, () => null); - const next = node.#parent; - node.#parent = null; // since we attached a handler already, we are no longer interested in what will happen later in the chain - node = next; - } - } const result = this.#result; - const res = new GreedyPromise((resolve, reject) => { + return new this.constructor((resolve, reject) => { const continuation = () => { let value = result[1]; let [handler, resolveFn] = result[0] === SUCCESS ? [onSuccess, resolve] : [onError, reject]; @@ -87,8 +64,50 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { } result.length ? continuation() : this.#callbacks.push(continuation); }); - res.#parent = this; - return res; + } + + catch(onError) { + return this.then(null, onError); + } + + finally(onFinally) { + let val; + return this.then( + (v) => { val = v; return onFinally(); }, + (e) => { val = this.constructor.reject(e); return onFinally() } + ).then(() => val); + } + + static #collect(promises, collector, done) { + let cnt = promises.length; + function clt() { + collector.apply(this, arguments); + if (--cnt <= 0 && done) done(); + } + promises.length === 0 && done ? done() : promises.forEach((p, i) => this.resolve(p).then( + (val) => clt(true, val, i), + (err) => clt(false, err, i) + )); + } + + static race(promises) { + return new this((resolve, reject) => { + this.#collect(promises, (success, result) => success ? resolve(result) : reject(result)); + }) + } + + static all(promises) { + return new this((resolve, reject) => { + let res = []; + this.#collect(promises, (success, val, i) => success ? res[i] = val : reject(val), () => resolve(res)); + }) + } + + static allSettled(promises) { + return new this((resolve) => { + let res = []; + this.#collect(promises, (success, val, i) => res[i] = success ? {status: 'fulfilled', value: val} : {status: 'rejected', reason: val}, () => resolve(res)) + }) } static resolve(value) { diff --git a/src/videoCache.js b/src/videoCache.js index f69a20f0139..88fc27625fd 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -9,8 +9,8 @@ * This trickery helps integrate with ad servers, which set character limits on request params. */ -import { ajax } from './ajax.js'; -import { config } from './config.js'; +import {ajaxBuilder} from './ajax.js'; +import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; /** @@ -142,11 +142,11 @@ function shimStorageCallback(done) { * @param {videoCacheStoreCallback} [done] An optional callback which should be executed after * the data has been stored in the cache. */ -export function store(bids, done) { +export function store(bids, done, getAjax = ajaxBuilder) { const requestData = { puts: bids.map(toStorageRequest) }; - + const ajax = getAjax(config.getConfig('cache.timeout')); ajax(config.getConfig('cache.url'), shimStorageCallback(done), JSON.stringify(requestData), { contentType: 'text/plain', withCredentials: true diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 6a72da8edce..03d0650effe 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -241,21 +241,23 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); - it('No bidder level configuration defined - default for video', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } + if (FEATURES.VIDEO) { + it('No bidder level configuration defined - default for video', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); + $$PREBID_GLOBAL$$.bidderSettings = {}; + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); }); - $$PREBID_GLOBAL$$.bidderSettings = {}; - let videoBid = utils.deepClone(bid); - videoBid.mediaType = 'video'; - videoBid.videoCacheKey = 'abc123def'; - - let expected = getDefaultExpected(videoBid); - let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); - assert.deepEqual(response, expected); - }); + } it('Custom configuration for all bidders', function () { $$PREBID_GLOBAL$$.bidderSettings = @@ -320,17 +322,18 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); - it('Custom configuration for all bidders with video bid', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } - }); - let videoBid = utils.deepClone(bid); - videoBid.mediaType = 'video'; - videoBid.videoCacheKey = 'abc123def'; + if (FEATURES.VIDEO) { + it('Custom configuration for all bidders with video bid', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; - $$PREBID_GLOBAL$$.bidderSettings = + $$PREBID_GLOBAL$$.bidderSettings = { standard: { adserverTargeting: [ @@ -396,11 +399,12 @@ describe('auctionmanager.js', function () { } }; - let expected = getDefaultExpected(videoBid); + let expected = getDefaultExpected(videoBid); - let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); - assert.deepEqual(response, expected); - }); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); + }); + } it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = @@ -1408,63 +1412,65 @@ describe('auctionmanager.js', function () { } describe('getMediaTypeGranularity', function () { - it('video', function () { - let mediaTypes = { video: {id: '1'} }; - - // mediaType is video and video.context is undefined - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - `` - expect(getMediaTypeGranularity('video', undefined, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes.video is undefined - mediaTypes = { banner: {} }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes is undefined - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - }); + if (FEATURES.VIDEO) { + it('video', function () { + let mediaTypes = { video: {id: '1'} }; + + // mediaType is video and video.context is undefined + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + `` + expect(getMediaTypeGranularity('video', undefined, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes.video is undefined + mediaTypes = { banner: {} }; + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes is undefined + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + }); - it('video-outstream', function () { - let mediaTypes = { video: { context: 'outstream' } }; + it('video-outstream', function () { + let mediaTypes = { video: { context: 'outstream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' - })).to.equal('high'); - }); + expect(getMediaTypeGranularity('video', mediaTypes, { + 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' + })).to.equal('high'); + }); - it('video-instream', function () { - let mediaTypes = { video: { context: 'instream' } }; + it('video-instream', function () { + let mediaTypes = { video: { context: 'instream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium', 'video-instream': 'high' - })).to.equal('high'); + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium', 'video-instream': 'high' + })).to.equal('high'); - // fall back to video if video-instream not found - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium' - })).to.equal('medium'); + // fall back to video if video-instream not found + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium' + })).to.equal('medium'); - expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { - banner: 'low', video: 'medium' - })).to.equal('medium'); - }); + expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { + banner: 'low', video: 'medium' + })).to.equal('medium'); + }); + } it('native', function () { expect(getMediaTypeGranularity('native', {native: {}}, { @@ -1566,34 +1572,36 @@ describe('auctionmanager.js', function () { }); }) - it('should call auction done after prebid cache is complete for mediaType video', function() { - bids[0].mediaType = 'video'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + if (FEATURES.VIDEO) { + it('should call auction done after prebid cache is complete for mediaType video', function() { + bids[0].mediaType = 'video'; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; - let opts = { - mediaType: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - } - }; - bidRequests = [ - mockBidRequest(bids[0], opts), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - ]; + let opts = { + mediaType: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + } + }; + bidRequests = [ + mockBidRequest(bids[0], opts), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + ]; - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - assert.equal(doneSpy.callCount, 0); - const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; - const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); - assert.equal(doneSpy.callCount, 1); - }); + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + assert.equal(doneSpy.callCount, 0); + const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; + const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); + assert.equal(doneSpy.callCount, 1); + }); + } it('should convert cpm to number', () => { auction.addBidReceived = sinon.spy(); diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 3363e1867d3..328846ca081 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -2,6 +2,8 @@ import {dep, enrichFPD} from '../../../src/fpd/enrichment.js'; import {hook} from '../../../src/hook.js'; import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {CLIENT_SECTIONS} from '../../../src/fpd/oneClient.js'; describe('FPD enrichment', () => { let sandbox; @@ -48,50 +50,86 @@ describe('FPD enrichment', () => { }); } - describe('site', () => { - it('sets page, ref, domain, and publisher.domain', () => { - const refererInfo = { - page: 'www.example.com', - ref: 'referrer.com' - }; - sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); - sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); - return fpd().then(ortb2 => { - sinon.assert.match(ortb2.site, { + CLIENT_SECTIONS.forEach(section => { + describe(`${section}, when set`, () => { + const ORTB2 = {[section]: {ext: {}}} + + it('sets domain and publisher.domain', () => { + const refererInfo = { page: 'www.example.com', - domain: 'example.com', - ref: 'referrer.com', - publisher: { - domain: 'publisher.example.com' - } + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); + sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); + return fpd(ORTB2).then(ortb2 => { + sinon.assert.match(ortb2[section], { + domain: 'example.com', + publisher: { + domain: 'publisher.example.com' + } + }); + }); + }) + + describe('keywords', () => { + let metaTag; + beforeEach(() => { + metaTag = document.createElement('meta'); + metaTag.name = 'keywords'; + metaTag.content = 'kw1, kw2'; + document.head.appendChild(metaTag); + }); + afterEach(() => { + document.head.removeChild(metaTag); + }); + + testWindows(() => window, () => { + it(`sets kewwords from meta tag`, () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].keywords).to.eql('kw1,kw2'); + }); + }); }); }); - }); - describe('keywords', () => { - let metaTag; + it('should not set keywords if meta tag is not present', () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].hasOwnProperty('keywords')).to.be.false; + }); + }); + }) + }) + + describe('site', () => { + describe('when mixed with app/dooh', () => { beforeEach(() => { - metaTag = document.createElement('meta'); - metaTag.name = 'keywords'; - metaTag.content = 'kw1, kw2'; - document.head.appendChild(metaTag); + sinon.stub(utils, 'logWarn'); }); + afterEach(() => { - document.head.removeChild(metaTag); + utils.logWarn.restore(); }); - testWindows(() => window, () => { - it(`sets kewwords from meta tag`, () => { - return fpd().then(ortb2 => { - expect(ortb2.site.keywords).to.eql('kw1,kw2'); - }); - }); - }); - }); + ['dooh', 'app'].forEach(prop => { + it(`should not be set when ${prop} is set`, () => { + return fpd({[prop]: {foo: 'bar'}}).then(ortb2 => { + expect(ortb2.site).to.not.exist; + sinon.assert.notCalled(utils.logWarn); // make sure we don't generate "both site and app are set" warnings + }) + }) + }) + }) - it('should not set keywords if meta tag is not present', () => { + it('sets page, ref', () => { + const refererInfo = { + page: 'www.example.com', + ref: 'referrer.com' + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); return fpd().then(ortb2 => { - expect(ortb2.site.hasOwnProperty('keywords')).to.be.false; + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'referrer.com', + }); }); }); @@ -106,6 +144,26 @@ describe('FPD enrichment', () => { expect(ortb2.site.publisher.domain).to.eql('pub.com'); }); }); + + it('respects config set through setConfig({site})', () => { + sandbox.stub(dep, 'getRefererInfo').callsFake(() => ({ + page: 'www.example.com', + ref: 'referrer.com', + })); + config.setConfig({ + site: { + ref: 'override.com', + priority: 'lower' + } + }); + return fpd({site: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'override.com', + priority: 'highest' + }) + }) + }) }); describe('device', () => { @@ -138,9 +196,44 @@ describe('FPD enrichment', () => { expect(ortb2.device.language).to.eql('lang'); }) }); + + it('respects setConfig({device})', () => { + win.navigator.userAgent = 'ua'; + win.navigator.language = 'lang'; + config.setConfig({ + device: { + language: 'override', + priority: 'lower' + } + }); + return fpd({device: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.device, { + language: 'override', + priority: 'highest', + ua: 'ua' + }) + }) + }) }); }); + describe('app', () => { + it('respects setConfig({app})', () => { + config.setConfig({ + app: { + priority: 'lower', + prop: 'value' + } + }); + return fpd({app: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.app, { + priority: 'highest', + prop: 'value' + }) + }) + }) + }) + describe('regs', () => { describe('gpc', () => { let win; @@ -210,4 +303,18 @@ describe('FPD enrichment', () => { }) }); }); + + it('leaves only one of app, site, dooh', () => { + return fpd({ + app: {p: 'val'}, + site: {p: 'val'}, + dooh: {p: 'val'} + }).then(ortb2 => { + expect(ortb2.app).to.not.exist; + expect(ortb2.site).to.not.exist; + sinon.assert.match(ortb2.dooh, { + p: 'val' + }) + }); + }) }); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/fpd/oneClient.js similarity index 79% rename from test/spec/ortbConverter/default_processors_spec.js rename to test/spec/fpd/oneClient.js index 48204b2c861..4ecde8d8a38 100644 --- a/test/spec/ortbConverter/default_processors_spec.js +++ b/test/spec/fpd/oneClient.js @@ -1,6 +1,7 @@ -import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; describe('onlyOneClientSection', () => { + const oneClient = clientSectionChecker(); [ [['app'], 'app'], [['site'], 'site'], @@ -11,13 +12,13 @@ describe('onlyOneClientSection', () => { ].forEach(([sections, winner]) => { it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); - onlyOneClientSection(req); + oneClient(req); expect(Object.keys(req)).to.eql([winner]); }) }); it('should not choke if none of the sections are in the request', () => { const req = {}; - onlyOneClientSection(req); + oneClient(req); expect(req).to.eql({}); }); }); diff --git a/test/spec/fpd/sua_spec.js b/test/spec/fpd/sua_spec.js index 121922fa78d..431f47268d3 100644 --- a/test/spec/fpd/sua_spec.js +++ b/test/spec/fpd/sua_spec.js @@ -6,7 +6,7 @@ import { SUA_SOURCE_UNKNOWN, suaFromUAData, uaDataToSUA -} from '../../../libraries/fpd/sua.js'; +} from '../../../src/fpd/sua.js'; describe('uaDataToSUA', () => { Object.entries({ diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 899ef392357..36e3f04b2a7 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -347,6 +347,76 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); + it('should force split keyword param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + splitKeyword: 1234 + }).build(); + const bid02 = new BidRequestBuilder().withParams({ + splitKeyword: ['1234'] + }).build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.equal('1234'); + expect(requests[0].data.adUnits[1].params.splitKeyword).to.not.exist; + }); + + it('should force key and value from data layer param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + dataLayer: { + 1234: 'dlparam', + goodkey: 1234, + objectvalue: { + random: 'result' + }, + arrayvalue: ['1234'] + } + }).build(); + + const bid02 = new BidRequestBuilder().withParams({ + dataLayer: 'a random string' + }).build(); + + const bid03 = new BidRequestBuilder().withParams({ + dataLayer: 1234 + }).build(); + + const bid04 = new BidRequestBuilder().withParams({ + dataLayer: ['an array'] + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02, bid03, bid04], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.dataLayer).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl).to.exist; + expect(requests[0].data.adUnits[0].params.dl['1234']).to.equal('dlparam'); + expect(requests[0].data.adUnits[0].params.dl.goodkey).to.equal('1234'); + expect(requests[0].data.adUnits[0].params.dl.objectvalue).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl.arrayvalue).to.not.exist; + + expect(requests[0].data.adUnits[1].params).to.exist; + expect(requests[0].data.adUnits[1].params.dl).to.not.exist; + expect(requests[0].data.adUnits[1].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[2].params).to.exist; + expect(requests[0].data.adUnits[2].params.dl).to.not.exist; + expect(requests[0].data.adUnits[2].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[3].params).to.exist; + expect(requests[0].data.adUnits[3].params.dl).to.not.exist; + expect(requests[0].data.adUnits[3].params.dataLayer).to.not.exist; + }); + describe('With video mediatype', function() { context('Outstream video', function() { it('should logWarn if user does not set renderer.backupOnly: true', function() { diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index eb560bb1bae..574f559e994 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -481,6 +481,17 @@ describe('Adf adapter', function () { nativeParams: { title: { required: true, len: 140 } }, + nativeOrtbRequest: { + assets: [ + { + required: 1, + id: 0, + title: { + len: 140 + } + } + ] + }, mediaTypes: { banner: { sizes: [[100, 100], [200, 300]] @@ -547,6 +558,57 @@ describe('Adf adapter', function () { describe('native', function () { describe('assets', function () { + it('should use nativeOrtbRequest instead of nativeParams or mediaTypes', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + nativeParams: { + title: { required: true, len: 200 }, + image: { required: true, sizes: [150, 150] }, + icon: { required: false, sizes: [150, 150] }, + body: { required: false, len: 1140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false }, + ortb: { + ver: '1.2', + assets: [] + } + }, + mediaTypes: { + native: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [150, 50] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false, len: 140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false } + } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 200 } }, + { required: 1, img: { type: 3, w: 170, h: 70 } }, + { required: 0, img: { type: 1, w: 70, h: 70 } }, + { required: 0, data: { type: 2, len: 150 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 200); + assert.deepEqual(assets[1].img, { type: 3, w: 170, h: 70 }); + assert.deepEqual(assets[2].img, { type: 1, w: 70, h: 70 }); + assert.deepEqual(assets[3].data, { type: 2, len: 150 }); + assert.deepEqual(assets[4].data, { type: 1 }); + assert.deepEqual(assets[5].data, { type: 12 }); + assert.ok(!assets[6]); + }); + it('should set correct asset id', function () { let validBidRequests = [{ bidId: 'bidId', @@ -555,14 +617,46 @@ describe('Adf adapter', function () { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 0, + img: { + type: 3, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: [ 'image/jpg', 'image/gif' ] + } + }, + { + id: 2, + data: { + type: 2, + len: 140 + } + } + ] } }]; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].id, 0); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 4); + assert.equal(assets[1].id, 1); + assert.equal(assets[2].id, 2); }); + it('should add required key if it is necessary', function () { let validBidRequests = [{ bidId: 'bidId', @@ -572,9 +666,16 @@ describe('Adf adapter', function () { image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 }, sponsoredBy: { required: true, len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 0, img: { type: 3, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] } }, + { data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1, len: 140 } } + ] } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].required, 1); @@ -593,8 +694,17 @@ describe('Adf adapter', function () { icon: { required: false, sizes: [50, 50] }, body: { required: false, len: 140 }, sponsoredBy: { required: true }, - cta: { required: false }, - clickUrl: { required: false } + cta: { required: false } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 1, img: { type: 3, w: 150, h: 50 } }, + { required: 0, img: { type: 1, w: 50, h: 50 } }, + { required: 0, data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] } }]; @@ -609,25 +719,6 @@ describe('Adf adapter', function () { assert.ok(!assets[6]); }); - describe('icon/image sizing', function () { - it('should flatten sizes and utilise first pair', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: { mid: 1000 }, - nativeParams: { - image: { - sizes: [[200, 300], [100, 200]] - }, - } - }]; - - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.w, 200); - assert.equal(assets[0].img.h, 300); - }); - }); - it('should utilise aspect_ratios', function () { const validBidRequests = [{ bidId: 'bidId', @@ -647,6 +738,12 @@ describe('Adf adapter', function () { ratio_width: 2 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, wmin: 100, ext: { aspectratios: ['1:3'] } } }, + { img: { type: 1, wmin: 10, ext: { aspectratios: ['2:5'] } } } + ] } }]; @@ -671,6 +768,14 @@ describe('Adf adapter', function () { icon: { aspect_ratios: [] } + }, + nativeOrtbRequest: { + request: { + assets: [ + { img: {} }, + { img: {} } + ] + } } }]; @@ -689,13 +794,18 @@ describe('Adf adapter', function () { ratio_width: 1 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, ext: { aspectratios: ['3:1'] } } } + ] } }]; let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 0); - assert.equal(assets[0].img.hmin, 0); + assert.ok(!assets[0].img.wmin); + assert.ok(!assets[0].img.hmin); assert.ok(!assets[1]); }); }); @@ -717,7 +827,7 @@ describe('Adf adapter', function () { let serverResponse = { body: { seatbid: [{ - bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}] + bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}] }, { bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] }] @@ -729,19 +839,23 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] @@ -756,11 +870,11 @@ describe('Adf adapter', function () { body: { seatbid: [{ bid: [ - {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}, + {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}, {impid: '4', native: {ver: '1.1', link: { url: 'link4' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}} ] }, { - bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] + bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 0, data: {value: 'Asset title text'}}]}}] }] } }; @@ -770,45 +884,53 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId3', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId4', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] }; bids = spec.interpretResponse(serverResponse, bidRequest).map(bid => { - const { requestId, native: { clickUrl } } = bid; - return [ requestId, clickUrl ]; + const { requestId, native: { ortb: { link: { url } } } } = bid; + return [ requestId, url ]; }); assert.equal(bids.length, 3); @@ -850,6 +972,34 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, { + id: 1, + required: 1, + img: { + type: 3, + wmin: 836, + hmin: 627, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + } + ] + }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -876,56 +1026,45 @@ describe('Adf adapter', function () { const bid = [ { impid: '1', - price: 93.1231, - crid: '12312312', native: { - assets: [ - { - data: null, - id: 0, - img: null, - required: 0, - title: {text: 'title', len: null}, - video: null - }, { - data: null, - id: 2, - img: {type: null, url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10}, - required: 0, - title: null, - video: null - }, { - data: null, - id: 3, - img: {type: null, url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 100, h: 100}, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'body'}, - id: 4, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'cta'}, - id: 1, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'sponsoredBy'}, - id: 5, - img: null, - required: 0, - title: null, - video: null + ver: '1.1', + assets: [{ + id: 1, + required: 0, + title: { + text: 'FLS Native' + } + }, { + id: 3, + required: 0, + data: { + value: 'Adform' } - ], - link: { url: 'clickUrl', clicktrackers: ['clickTracker1', 'clickTracker2'] }, - imptrackers: ['imptrackers url1', 'imptrackers url2'], + }, { + id: 2, + required: 0, + data: { + value: 'Native banner. WOW.' + } + }, { + id: 4, + required: 0, + data: { + value: 'Oho' + } + }, { + id: 5, + required: 0, + img: { url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10 } + }, { + id: 0, + required: 0, + img: { url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 300, h: 300 } + }], + link: { + url: 'clickUrl', clicktrackers: [ 'clickTracker1', 'clickTracker2' ] + }, + imptrackers: ['imptracker url1', 'imptracker url2'], jstracker: 'jstracker' } } @@ -940,24 +1079,66 @@ describe('Adf adapter', function () { }; let bidRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bids: [{ + bidId: 'bidId1', + nativeOrtbRequest: { + ver: '1.2', + assets: [{ + id: 0, + required: 1, + img: { + type: 3, + wmin: 200, + hmin: 166, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 1, + required: 1, + title: { + len: 150 + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + }, { + id: 3, + required: 1, + data: { + type: 1 + } + }, { + id: 4, + required: 1, + data: { + type: 12 + } + }, { + id: 5, + required: 0, + img: { + type: 1, + wmin: 10, + hmin: 10, + ext: { + aspectratios: ['1:1'] + } + } + }] + }, + }] }; const result = spec.interpretResponse(serverResponse, bidRequest)[0].native; const native = bid[0].native; const assets = native.assets; - assert.deepEqual({ - clickUrl: native.link.url, - clickTrackers: native.link.clicktrackers, - impressionTrackers: native.imptrackers, - javascriptTrackers: [ native.jstracker ], - title: assets[0].title.text, - icon: {url: assets[1].img.url, width: assets[1].img.w, height: assets[1].img.h}, - image: {url: assets[2].img.url, width: assets[2].img.w, height: assets[2].img.h}, - body: assets[3].data.value, - cta: assets[4].data.value, - sponsoredBy: assets[5].data.value - }, result); + + assert.deepEqual(result, {ortb: native}); }); it('should return empty when there is no bids in response', function () { const serverResponse = { diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 170982a51bd..ba9261e1aba 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -235,6 +235,7 @@ describe('Adloox Ad Server Video', function () { it('should fetch, retry on withoutCredentials, follow and return a wrapped blob that expires', function (done) { BID.responseTimestamp = utils.timestamp(); BID.ttl = 30; + this.timeout(5000) const clock = sandbox.useFakeTimers(BID.responseTimestamp); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index d0ef69ccf08..c57e51c44fc 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -11,16 +11,17 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/' ]; const aliasEP = { - appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', - appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', - onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', - navelix: 'https://ghb.hb.navelix.com/v2/auction/', - bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', - streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', - janet: 'https://ghb.bidder.jmgads.com/v2/auction/', - pgam: 'https://ghb.pgamssp.com/v2/auction/', - ocm: 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', - vidcrunchllc: 'https://ghb.platform.vidcrunch.com/v2/auction/', + 'appaloosa': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'appaloosa_publisherSuffix': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'onefiftytwomedia': 'https://ghb.ads.152media.com/v2/auction/', + 'navelix': 'https://ghb.hb.navelix.com/v2/auction/', + 'bidsxchange': 'https://ghb.hbd.bidsxchange.com/v2/auction/', + 'streamkey': 'https://ghb.hb.streamkey.net/v2/auction/', + 'janet': 'https://ghb.bidder.jmgads.com/v2/auction/', + 'pgam': 'https://ghb.pgamssp.com/v2/auction/', + 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', + 'vidcrunchllc': 'https://ghb.platform.vidcrunch.com/v2/auction/', + '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js index 5b1df6f9d4c..c587ef1a133 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -110,12 +110,18 @@ describe('airgrid RTD Submodule', function () { .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); - const bidderOrtb2 = agRTD.getAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); - const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + agRTD.setAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); + + const bidderConfig = config.getBidderConfig() + const bidders = RTD_CONFIG.dataProviders[0].params.bidders + const bidderOrtb2 = bidderConfig + Object.keys(bidderOrtb2).forEach((bidder) => { if (bidders.indexOf(bidder) === -1) return; - expect(deepAccess(bidderOrtb2[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); + MATCHED_AUDIENCES.forEach((audience) => { + expect(deepAccess(bidderOrtb2[bidder], 'ortb2.site.keywords')).to.contain(audience); + }) }); }); diff --git a/test/spec/modules/browsiBidAdapter_spec.js b/test/spec/modules/browsiBidAdapter_spec.js new file mode 100644 index 00000000000..8892396adac --- /dev/null +++ b/test/spec/modules/browsiBidAdapter_spec.js @@ -0,0 +1,209 @@ +import {ENDPOINT, spec} from 'modules/browsiBidAdapter.js'; +import {config} from 'src/config.js'; +import {VIDEO, BANNER} from 'src/mediaTypes.js'; + +const {expect} = require('chai'); +const DATA = 'brwvidtag'; +const ADAPTER = '__bad'; + +describe('browsi Bid Adapter Test', function () { + describe('isBidRequestValid', function () { + let mediaTypes; + let bid; + beforeEach(function () { + mediaTypes = {}; + mediaTypes[VIDEO] = {}; + bid = { + 'params': { + 'pubId': '1234567', + 'tagId': '1' + }, + 'mediaTypes': mediaTypes + }; + }); + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when missing pubId', function () { + delete bid.params.pubId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing tagId', function () { + delete bid.params.tagId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing params', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when params have invalid type', function () { + bid.params.tagId = 1; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when video mediaType is missing', function () { + delete bid.mediaTypes[VIDEO]; + bid.mediaTypes[BANNER] = {} + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequest; + let bidderRequest; + beforeEach(function () { + window[DATA] = {} + window[DATA][ADAPTER] = {index: 0}; + bidRequest = [ + { + 'params': { + 'pubId': 'browiPubId1', + 'tagId': '2' + }, + 'adUnitCode': 'adUnitCode1', + 'auctionId': 'auctionId1', + 'sizes': [640, 480], + 'bidId': '12345678', + 'requestId': '1234567-3456-4562-7689-98765434A', + 'transactionId': '1234567-3456-4562-7689-98765434B', + 'schain': {}, + 'mediaTypes': {video: {playerSize: [640, 480]}} + } + ]; + bidderRequest = { + 'bidderRequestId': 'bidderRequestId1', + 'refererInfo': { + 'canonicalUrl': null, + 'page': 'https://browsi.com', + 'domain': 'browsi.com', + 'ref': null, + 'numIframes': 0, + 'reachedTop': true, + 'isAmp': false, + 'stack': ['https://browsi.com'] + }, + 'gdprConsent': { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true + }, + 'uspConsent': '1YYY' + }; + }); + afterEach(function() { + window[DATA] = undefined; + config.resetConfig(); + }); + it('should return an array of requests with single request', function () { + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests.length).to.equal(1); + const request = requests[0]; + const inputRequest = bidRequest[0]; + const requestToExpect = { + method: 'POST', + url: ENDPOINT, + data: { + requestId: bidderRequest.bidderRequestId, + bidId: inputRequest.bidId, + timeout: 3000, + baData: window[DATA][ADAPTER], + referer: bidderRequest.refererInfo.page, + gdpr: bidderRequest.gdprConsent, + ccpa: bidderRequest.uspConsent, + sizes: inputRequest.sizes, + video: {playerSize: [640, 480]}, + aUCode: inputRequest.adUnitCode, + aID: inputRequest.auctionId, + tID: inputRequest.transactionId, + schain: inputRequest.schain, + params: inputRequest.params + } + } + assert.deepEqual(request, requestToExpect); + }); + it('should pass on timeout in bidderRequest', function() { + bidderRequest.timeout = 8000; + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests[0].data.timeout).to.equal(8000); + }); + it('should pass timeout in config', function() { + config.setConfig({'bidderTimeout': 6000}); + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests[0].data.timeout).to.equal(6000); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + 'url': ENDPOINT, + 'data': { + 'bidId': 'bidId1', + } + }; + let serverResponse = {}; + serverResponse.body = { + bidId: 'bidId1', + w: 300, + h: 250, + vXml: 'vastXml', + vUrl: 'vastUrl', + cpm: 1, + cur: 'USD', + ttl: 10000, + someExtraParams: 8, + } + + it('should return a valid response', function () { + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + const actualBidResponse = bidResponses[0]; + const expectedBidResponse = { + requestId: bidRequest.data.bidId, + bidderCode: 'browsi', + bidId: 'bidId1', + width: 300, + height: 250, + vastXml: 'vastXml', + vastUrl: 'vastUrl', + cpm: 1, + currency: 'USD', + ttl: 10000, + someExtraParams: 8, + mediaType: VIDEO + }; + + assert.deepEqual(actualBidResponse, expectedBidResponse); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse = { + userSyncs: [ + {url: 'syncUrl1', type: 'image'}, + {url: 'http://syncUrl2', type: 'iframe'} + ] + } + let serverResponse = [ + {body: bidResponse} + ]; + it('should return iframe type userSync', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse[0]); + expect(userSyncs.length).to.equal(1); + let userSync = userSyncs[0]; + expect(userSync.url).to.equal('http://syncUrl2'); + expect(userSync.type).to.equal('iframe'); + }); + it('should return image type userSyncs', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse[0]); + let userSync = userSyncs[0]; + expect(userSync.url).to.equal('http://syncUrl1'); + expect(userSync.type).to.equal('image'); + }); + it('should handle multiple server responses', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); + expect(userSyncs.length).to.equal(1); + }); + it('should return empty userSyncs', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); + expect(userSyncs.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 8c2ac34350b..a19cdf7fd35 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -2,96 +2,295 @@ import {expect} from 'chai'; import {spec, helper} from 'modules/cleanmedianetBidAdapter.js'; import * as utils from 'src/utils.js'; import {newBidder} from '../../../src/adapters/bidderFactory.js'; +import {deepClone} from 'src/utils'; const supplyPartnerId = '123'; const adapter = newBidder(spec); -describe('CleanmedianetAdapter', function () { - describe('Is String start with search ', function () { - it('check if a string started with', function () { - expect(helper.startsWith('cleanmediaads.com', 'cleanmediaads')).to.equal( - true - ); +const TTL = 360; + +describe('CleanmedianetAdapter', () => { + let schainConfig, + bidRequest, + bannerBidRequest, + videoBidRequest, + rtbResponse, + videoResponse, + gdprConsent; + + beforeEach(() => { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + + bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + refererInfo: {referer: 'http://examplereferer.com'}, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + }, + schain: schainConfig, + uspConsent: 'cleanmediaCCPA' + }; + + bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'utrk': [ + {'type': 'iframe', 'url': '//bidder.cleanmediaads.com/user/sync/1?gdpr=[GDPR]&consent=[CONSENT]&usp=[US_PRIVACY]'}, + {'type': 'image', 'url': '//bidder.cleanmediaads.com/user/sync/2'} + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': '<?xml version="1.0" encoding="UTF-8"?><VAST version="2.0">↵<Ad id="99aaef40-bbc0-4829-8599-f5313ea27ee9">↵<Wrapper>↵<AdSystem version="2.0"><![CDATA[cleanmedia.net]]></AdSystem>↵<VASTAdTagURI><![CDATA[https://static.gambid.io/demo/vast.xml]]></VASTAdTagURI>↵<Error><![CDATA[https://bidder.cleanmediaads.com/pix/1707/verror/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1&error=[ERRORCODE]]]></Error>↵<Impression><![CDATA[https://bidder.cleanmediaads.com/pix/1707/vimp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Impression>↵<Creatives>↵<Creative AdID="1274">↵<Linear>↵<TrackingEvents>↵<Tracking event="start"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/start/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="firstQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/fq/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="midpoint"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/mp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="thirdQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/tq/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="complete"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/comp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵</TrackingEvents>↵</Linear>↵</Creative>↵</Creatives>↵</Wrapper></Ad>↵</VAST>', + 'adomain': ['aaa.com'], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'vast_url': 'http://my.vast.com', + 'utrk': [ + {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'} + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': '<img width="300px" height="250px" src="https://dummyimage.com/300x250/030d00/52b31e.gif&text=CleanMedia+Demo" onclick="window.open(\'https://www.cleanmedia.net\')"> <img width="0px" height="0px" src="https://bidder.cleanmediaads.com/pix/monitoring/imp/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0"/>', + 'adomain': ['bbb.com'], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'utrk': [ + {'type': 'image', 'url': '//p.partner2.io/user/sync/1'} + ] + } + } + ] + } + ] + }; + + videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + 'adomain': [], + 'adm': '<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"2.0\">\n<Ad id=\"afbb5852-7cea-4a81-aa9a-a41aab505c23\">\n<Wrapper>\n<AdSystem version=\"2.0\"><![CDATA[cleanmedia.net]]></AdSystem>\n<VASTAdTagURI><![CDATA[https://static.gambid.io/demo/vast.xml]]></VASTAdTagURI>\n<Error><![CDATA[https://bidder.cleanmediaads.com/pix/1275/verror/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&error=[ERRORCODE]]]></Error>\n<Impression><![CDATA[https://bidder.cleanmediaads.com/pix/1275/vimp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Impression>\n<Creatives>\n<Creative AdID=\"1274\">\n<Linear>\n<TrackingEvents>\n<Tracking event=\"start\"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/start/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event=\"firstQuartile\"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/fq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event=\"midpoint\"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/mp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event=\"thirdQuartile\"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/tq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event=\"complete\"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/comp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n</TrackingEvents>\n</Linear>\n</Creative>\n</Creatives>\n</Wrapper>\n</Ad>\n</VAST>\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=[GDPR]&consent=[CONSENT]&us_privacy=[US_PRIVACY]' + }] + } + }; + + gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + }); + + describe('Get top Frame', () => { + it('check if you are in the top frame', () => { + expect(helper.getTopFrame()).to.equal(0); + }); + }); + + describe('Is String start with search', () => { + it('check if a string started with', () => { + expect(helper.startsWith('cleanmedia.net', 'clea')).to.equal(true); }); }); - describe('inherited functions', function() { - it('exists and is a function', function() { + describe('inherited functions', () => { + it('exists and is a function', () => { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { - it('should validate supply-partner ID', function() { - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: 123 } }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); + describe('isBidRequestValid', () => { + it('should validate supply-partner ID', () => { + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: 123}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); + }); + + it('should validate RTB endpoint', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // RTB endpoint has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', rtbEndpoint: 123}})).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyPartnerId: '123', + rtbEndpoint: 'https://some.url.com' + } + })).to.equal(true); + }); + + it('should validate bid floor', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // bidfloor has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: 0.1}})).to.equal(true); + + const getFloorResponse = {currency: 'USD', floor: 5}; + let testBidRequest = deepClone(bidRequest); + let request = spec.buildRequests([testBidRequest], bidRequest)[0]; + + // 1. getBidFloor not exist AND bidfloor not exist - return 0 + let payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.equal(0); + + // 2. getBidFloor not exist AND bidfloor exist - use bidfloor property + testBidRequest = deepClone(bidRequest); + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(0.3) + + // 3. getBidFloor exist AND bidfloor not exist - use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) + + // 4. getBidFloor exist AND bidfloor exist -> use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) }); - it('should validate adpos', function() { - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); // adpos has a default - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', adpos: '123' } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', adpos: 0.1 } - }) - ).to.equal(true); + it('should validate adpos', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: 0.1}})).to.equal(true); }); - it('should validate instl', function() { - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); // adpos has a default - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', instl: '123' } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', instl: -1 } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } }) - ).to.equal(true); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } }) - ).to.equal(true); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } }) - ).to.equal(false); + it('should validate instl', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: -1}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 0}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 1}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 2}})).to.equal(false); }); }); - describe('buildRequests', function() { - const bidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - banner: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - refererInfo: { referer: 'https://examplereferer.com', domain: 'examplereferer.com' }, - gdprConsent: { - consentString: 'some string', - gdprApplies: true - } - }; - it('returns an array', function() { + describe('buildRequests', () => { + it('returns an array', () => { let response; response = spec.buildRequests([]); expect(Array.isArray(response)).to.equal(true); @@ -99,58 +298,61 @@ describe('CleanmedianetAdapter', function () { response = spec.buildRequests([bidRequest], bidRequest); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { - auctionId: '1', - adUnitCode: 'a' - }); - const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { - auctionId: '1', - adUnitCode: 'b' - }); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'}); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'}); response = spec.buildRequests([adUnit1, adUnit2], bidRequest); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(2); }); - it('builds request correctly', function() { + it('targets correct endpoint', () => { + let response; + response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://bidder\\.cleanmediaads\\.com/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + expect(response.data.id).to.equal(bidRequest.auctionId); + const bidRequestWithEndpoint = utils.deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://bidder.cleanmediaads.com/a12'; + response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; + expect(response.url).to.match(new RegExp(`^https://bidder\\.cleanmediaads\\.com/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + }); + + it('builds request correctly', () => { let bidRequest2 = utils.deepClone(bidRequest); Object.assign(bidRequest2.refererInfo, { - page: 'https://www.test.com/page.html', - domain: 'test.com', - ref: 'https://referer.com' + page: 'http://www.test.com/page.html', + domain: 'www.test.com', + ref: 'http://referrer.com' }) - let response = spec.buildRequests([bidRequest], bidRequest2)[0]; - expect(response.data.site.domain).to.equal('test.com'); - expect(response.data.site.page).to.equal('https://www.test.com/page.html'); - expect(response.data.site.ref).to.equal('https://referer.com'); + + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://referrer.com'); expect(response.data.imp.length).to.equal(1); expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); expect(response.data.imp[0].instl).to.equal(0); expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); expect(response.data.imp[0].bidfloor).to.equal(0); expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + expect(response.data.regs.ext.us_privacy).to.equal('cleanmediaCCPA');// USP/CCPAs + expect(response.data.source.ext.schain).to.deep.equal(bidRequest2.schain); + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); bidRequestWithInstlEquals1.params.instl = 1; - response = spec.buildRequests( - [bidRequestWithInstlEquals1], - bidRequest2 - )[0]; - expect(response.data.imp[0].instl).to.equal( - bidRequestWithInstlEquals1.params.instl - ); + response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl); const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); bidRequestWithInstlEquals0.params.instl = 1; - response = spec.buildRequests( - [bidRequestWithInstlEquals0], - bidRequest2 - )[0]; - expect(response.data.imp[0].instl).to.equal( - bidRequestWithInstlEquals0.params.instl - ); + response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0]; + expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); }); - it('builds request banner object correctly', function() { + it('builds request banner object correctly', () => { let response; const bidRequestWithBanner = utils.deepClone(bidRequest); bidRequestWithBanner.mediaTypes = { @@ -159,54 +361,80 @@ describe('CleanmedianetAdapter', function () { } }; response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; - expect(response.data.imp[0].banner.w).to.equal( - bidRequestWithBanner.mediaTypes.banner.sizes[0][0] - ); - expect(response.data.imp[0].banner.h).to.equal( - bidRequestWithBanner.mediaTypes.banner.sizes[0][1] - ); + expect(response.data.imp[0].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][0]); + expect(response.data.imp[0].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][1]); expect(response.data.imp[0].banner.pos).to.equal(0); + expect(response.data.imp[0].banner.topframe).to.equal(0); const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); bidRequestWithPosEquals1.params.pos = 1; response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; - expect(response.data.imp[0].banner.pos).to.equal( - bidRequestWithPosEquals1.params.pos - ); + expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); }); - it('builds request video object correctly', function() { + it('builds request video object correctly', () => { let response; const bidRequestWithVideo = utils.deepClone(bidRequest); + + bidRequestWithVideo.params.video = { + placement: 1, + minduration: 1, + } + bidRequestWithVideo.mediaTypes = { video: { - sizes: [[300, 250], [120, 600]] + playerSize: [[302, 252]], + mimes: ['video/mpeg'], + playbackmethod: 1, + startdelay: 1, } }; response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[0].video.w).to.equal( - bidRequestWithVideo.mediaTypes.video.sizes[0][0] - ); - expect(response.data.imp[0].video.h).to.equal( - bidRequestWithVideo.mediaTypes.video.sizes[0][1] - ); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); expect(response.data.imp[0].video.pos).to.equal(0); + + expect(response.data.imp[0].video.mimes).to.equal(bidRequestWithVideo.mediaTypes.video.mimes); + expect(response.data.imp[0].video.skip).to.not.exist; + expect(response.data.imp[0].video.placement).to.equal(1); + expect(response.data.imp[0].video.minduration).to.equal(1); + expect(response.data.imp[0].video.playbackmethod).to.equal(1); + expect(response.data.imp[0].video.startdelay).to.equal(1); + + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [302, 252], + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, + }, + }; + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[1]); + bidRequestWithPosEquals1.params.pos = 1; response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; - expect(response.data.imp[0].video.pos).to.equal( - bidRequestWithPosEquals1.params.pos - ); + expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); }); - it('builds request video object correctly with context', function() { - let response; + it('builds request video object correctly with context', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes = { video: { - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, } }; - response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal('instream'); bidRequestWithVideo.mediaTypes.video.context = 'outstream'; @@ -220,358 +448,185 @@ describe('CleanmedianetAdapter', function () { response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal(null); }); - it('builds request video object correctly with multi-dimensions size array', function () { - let bidRequestWithVideo = utils.deepClone(bidRequest); + + it('builds request video object correctly with multi-dimensions size array', () => { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes.video = { playerSize: [[304, 254], [305, 255]], - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, }; - let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[1].video.w).to.equal(304); - expect(response.data.imp[1].video.h).to.equal(254); + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; - bidRequestWithVideo = utils.deepClone(bidRequest); - bidRequestWithVideo.mediaTypes.video = { - playerSize: [304, 254] - }; + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('outstream'); - response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[1].video.w).to.equal(304); - expect(response.data.imp[1].video.h).to.equal(254); + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal(null); }); - it('builds request with gdpr consent', function() { + it('builds request with gdpr consent', () => { let response = spec.buildRequests([bidRequest], bidRequest)[0]; + + expect(response.data.ext.gdpr_consent).to.not.equal(null).and.not.equal(undefined); expect(response.data.ext).to.have.property('gdpr_consent'); - expect(response.data.ext.gdpr_consent.consent_string).to.equal( - 'some string' - ); + expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string'); expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); - }); - }); - - describe('interpretResponse', function() { - const bannerBidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - banner: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - bidId: '111', - refererInfo: { referer: 'https://examplereferer.com' } - }; - const videoBidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - video: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - bidId: '111', - refererInfo: { referer: 'https://examplereferer.com' } - }; + expect(response.data.regs.ext.gdpr).to.not.equal(null).and.not.equal(undefined); + expect(response.data.user.ext.consent).to.equal('some string'); + }); - const rtbResponse = { - id: 'imp_5b05b9fde4b09084267a556f', - bidid: 'imp_5b05b9fde4b09084267a556f', - cur: 'USD', - ext: { - utrk: [ - { type: 'iframe', url: '//bidder.cleanmediaads.com/user/sync/1' }, - { type: 'image', url: '//bidder.cleanmediaads.com/user/sync/2' } - ] - }, - seatbid: [ - { - seat: 'seat1', - group: 0, - bid: [ - { - id: '0', - impid: '1', - price: 2.016, - adid: '579ef31bfa788b9d2000d562', - nurl: - 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', - adm: - '<?xml version="1.0" encoding="UTF-8"?><VAST version="2.0">↵<Ad id="99aaef40-bbc0-4829-8599-f5313ea27ee9">↵<Wrapper>↵<AdSystem version="2.0"><![CDATA[cleanmediaads.com]]></AdSystem>↵<VASTAdTagURI><![CDATA[https://static.gambid.io/demo/vast.xml]]></VASTAdTagURI>↵<Error><![CDATA[https://bidder.cleanmediaads.com/pix/1707/verror/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1&error=[ERRORCODE]]]></Error>↵<Impression><![CDATA[https://bidder.cleanmediaads.com/pix/1707/vimp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Impression>↵<Creatives>↵<Creative AdID="1274">↵<Linear>↵<TrackingEvents>↵<Tracking event="start"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/start/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="firstQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/fq/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="midpoint"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/mp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="thirdQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/tq/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵<Tracking event="complete"><![CDATA[https://bidder.cleanmediaads.com/pix/1707/comp/imp_5c4dcd32e4b044440442782e/im.gif?r=imp_5c4dcd32e4b044440442782e&i=99aaef40-bbc0-4829-8599-f5313ea27ee9&a=1274&b=gb_1]]></Tracking>↵</TrackingEvents>↵</Linear>↵</Creative>↵</Creatives>↵</Wrapper></Ad>↵</VAST>', - adomain: ['aaa.com'], - cid: '579ef268fa788b9d2000d55c', - crid: '579ef31bfa788b9d2000d562', - attr: [], - h: 600, - w: 120, - ext: { - vast_url: 'https://my.vast.com', - utrk: [{ type: 'iframe', url: '//p.partner1.io/user/sync/1' }] - } - } - ] - }, - { - seat: 'seat2', - group: 0, - bid: [ - { - id: '1', - impid: '1', - price: 3, - adid: '542jlhdfd2112jnjf3x', - nurl: - 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', - adm: - '<img width="300px" height="250px" src="https://dummyimage.com/300x250/030d00/52b31e.gif&text=Cleanmedia+Demo" onclick="window.open(\'https://www.cleanmediaads.com\')"> <img width="0px" height="0px" src="https://bidder.cleanmediaads.com/pix/monitoring/imp/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0"/>', - adomain: ['bbb.com'], - cid: 'fgdlwjh2498ydjhg1', - crid: 'kjh34297ydh2133d', - attr: [], - h: 250, - w: 300, - ext: { - utrk: [{ type: 'image', url: '//p.partner2.io/user/sync/1' }] - } - } - ] - } - ] - }; + it('build request with ID5 Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.id5id = { uid: 'id5-user-id' }; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'ext': { + 'rtiPartner': 'ID5ID' + } + }] + }]); + }); - it('returns an empty array on missing response', function() { - let response; + it('build request with unified Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.tdid = 'tdid-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'tdid-user-id', + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + }); - response = spec.interpretResponse(undefined, { - bidRequest: bannerBidRequest - }); + describe('interpretResponse', () => { + it('returns an empty array on missing response', () => { + let response = spec.interpretResponse(undefined, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(0); - response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); + response = spec.interpretResponse({}, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(0); }); - it('aggregates banner bids from all seat bids', function() { - const response = spec.interpretResponse( - { body: rtbResponse }, - { bidRequest: bannerBidRequest } - ); + it('aggregates banner bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const ad0 = response[0]; expect(ad0.requestId).to.equal(bannerBidRequest.bidId); expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); - expect(ad0.ttl).to.equal(360); + expect(ad0.ttl).to.equal(TTL); expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal( - rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD' - ); + expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD'); expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); expect(ad0.vastXml).to.be.an('undefined'); expect(ad0.vastUrl).to.be.an('undefined'); + expect(ad0.meta.advertiserDomains).to.be.equal(rtbResponse.seatbid[1].bid[0].adomain); }); - it('aggregates video bids from all seat bids', function() { - const response = spec.interpretResponse( - { body: rtbResponse }, - { bidRequest: videoBidRequest } - ); + it('aggregates video bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const ad0 = response[0]; expect(ad0.requestId).to.equal(videoBidRequest.bidId); expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); - expect(ad0.ttl).to.equal(360); + expect(ad0.ttl).to.equal(TTL); expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal( - rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD' - ); + expect(ad0.currency).to.equal(rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD'); expect(ad0.ad).to.be.an('undefined'); expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); }); - it('aggregates user-sync pixels', function() { - const response = spec.getUserSyncs({}, [{ body: rtbResponse }]); + it('aggregates user-sync pixels', () => { + const response = spec.getUserSyncs({}, [{body: rtbResponse}]); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(4); expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); - expect(response[0].url).to.equal( - rtbResponse.ext.utrk[0].url + '?gc=missing' - ); + expect(response[0].url).to.equal('//bidder.cleanmediaads.com/user/sync/1?gdpr=0&consent=&usp='); expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); - expect(response[1].url).to.equal( - rtbResponse.ext.utrk[1].url + '?gc=missing' - ); - expect(response[2].type).to.equal( - rtbResponse.seatbid[0].bid[0].ext.utrk[0].type - ); - expect(response[2].url).to.equal( - rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing' - ); - expect(response[3].type).to.equal( - rtbResponse.seatbid[1].bid[0].ext.utrk[0].type - ); - expect(response[3].url).to.equal( - rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing' - ); + expect(response[1].url).to.equal('//bidder.cleanmediaads.com/user/sync/2'); + expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type); + expect(response[2].url).to.equal('//p.partner1.io/user/sync/1'); + expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type); + expect(response[3].url).to.equal('//p.partner2.io/user/sync/1'); }); - it('supports configuring outstream renderers', function() { - const videoResponse = { - id: '64f32497-b2f7-48ec-9205-35fc39894d44', - bidid: 'imp_5c24924de4b0d106447af333', - cur: 'USD', - seatbid: [ - { - seat: '3668', - group: 0, - bid: [ - { - id: 'gb_1', - impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', - price: 5.0, - adid: '1274', - nurl: - 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', - adomain: [], - adm: - '<?xml version="1.0" encoding="UTF-8"?><VAST version="2.0">\n<Ad id="afbb5852-7cea-4a81-aa9a-a41aab505c23">\n<Wrapper>\n<AdSystem version="2.0"><![CDATA[cleanmediaads.com]]></AdSystem>\n<VASTAdTagURI><![CDATA[https://static.gambid.io/demo/vast.xml]]></VASTAdTagURI>\n<Error><![CDATA[https://bidder.cleanmediaads.com/pix/1275/verror/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&error=[ERRORCODE]]]></Error>\n<Impression><![CDATA[https://bidder.cleanmediaads.com/pix/1275/vimp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Impression>\n<Creatives>\n<Creative AdID="1274">\n<Linear>\n<TrackingEvents>\n<Tracking event="start"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/start/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="firstQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/fq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="midpoint"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/mp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="thirdQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/tq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="complete"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/comp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n</TrackingEvents>\n</Linear>\n</Creative>\n</Creatives>\n</Wrapper>\n</Ad>\n</VAST>\n', - cid: '3668', - crid: '1274', - cat: [], - attr: [], - h: 250, - w: 300, - ext: { - vast_url: - 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', - imptrackers: [ - 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' - ] - } - } - ] - } - ], - ext: { - utrk: [ - { - type: 'image', - url: - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' - } - ] - } - }; + it('supports configuring outstream renderers', () => { const videoRequest = utils.deepClone(videoBidRequest); videoRequest.mediaTypes.video.context = 'outstream'; - const result = spec.interpretResponse( - { body: videoResponse }, - { bidRequest: videoRequest } - ); + const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest}); expect(result[0].renderer).to.not.equal(undefined); }); - it('validates in/existing of gdpr consent', function() { - let videoResponse = { - id: '64f32497-b2f7-48ec-9205-35fc39894d44', - bidid: 'imp_5c24924de4b0d106447af333', - cur: 'USD', - seatbid: [ - { - seat: '3668', - group: 0, - bid: [ - { - id: 'gb_1', - impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', - price: 5.0, - adid: '1274', - nurl: - 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', - adomain: [], - adm: - '<?xml version="1.0" encoding="UTF-8"?><VAST version="2.0">\n<Ad id="afbb5852-7cea-4a81-aa9a-a41aab505c23">\n<Wrapper>\n<AdSystem version="2.0"><![CDATA[cleanmediaads.com]]></AdSystem>\n<VASTAdTagURI><![CDATA[https://static.gambid.io/demo/vast.xml]]></VASTAdTagURI>\n<Error><![CDATA[https://bidder.cleanmediaads.com/pix/1275/verror/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&error=[ERRORCODE]]]></Error>\n<Impression><![CDATA[https://bidder.cleanmediaads.com/pix/1275/vimp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Impression>\n<Creatives>\n<Creative AdID="1274">\n<Linear>\n<TrackingEvents>\n<Tracking event="start"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/start/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="firstQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/fq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="midpoint"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/mp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="thirdQuartile"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/tq/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n<Tracking event="complete"><![CDATA[https://bidder.cleanmediaads.com/pix/1275/comp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1]]></Tracking>\n</TrackingEvents>\n</Linear>\n</Creative>\n</Creatives>\n</Wrapper>\n</Ad>\n</VAST>\n', - cid: '3668', - crid: '1274', - cat: [], - attr: [], - h: 250, - w: 300, - ext: { - vast_url: - 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', - imptrackers: [ - 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' - ] - } - } - ] - } - ], - ext: { - utrk: [ - { - type: 'image', - url: - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' - } - ] - } - }; - let gdprConsent = { - gdprApplies: true, - consentString: 'consent string' - }; - let result = spec.getUserSyncs( - {}, - [{ body: videoResponse }], - gdprConsent - ); + it('validates in/existing of gdpr consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=consent%20string' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=cleanmediaCCPA'); gdprConsent.gdprApplies = false; - result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=missing' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy=cleanmediaCCPA'); - videoResponse.ext.utrk[0].url = - 'https://bidder.cleanmediaads.com/pix/1275/scm'; - result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + videoResponse.ext.utrk[0].url = 'https://bidder.cleanmediaads.com/pix/1275/scm'; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm'); + }); + + it('validates existence of gdpr, gdpr consent and usp consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=cleanmediaCCPA'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, ''); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?gc=missing' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy='); }); }); }); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..d5e7140a9f7 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +112,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -138,6 +139,14 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); + it('should not generate uid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, { @@ -213,82 +222,4 @@ describe('ConcertAdapter', function () { expect(bids).to.have.lengthOf(0); }); }); - - describe('spec.getUserSyncs', function() { - it('should not register syncs when iframe is not enabled', function() { - const opts = { - iframeEnabled: false - } - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should not register syncs when the user has opted out', function() { - const opts = { - iframeEnabled: true - }; - storage.setDataInLocalStorage('c_nap', 'true'); - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: true - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=1'); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=0'); - }); - - it('should set gdpr consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('should set ccpa consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - uspConsent: '1YYY' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('usp_consent=1YY'); - }); - }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 48b48c987e5..2cd3d011e1d 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -146,16 +146,17 @@ describe('consentManagement', function () { }); describe('static consent string setConsentConfig value', () => { - afterEach(() => { - config.resetConfig(); - }); + Object.entries({ + 'getTCData': (cfg) => ({getTCData: cfg}), + 'consent data directly': (cfg) => cfg, + }).forEach(([t, packageCfg]) => { + describe(`using ${t}`, () => { + afterEach(() => { + config.resetConfig(); + }); - it('results in user settings overriding system defaults for v2 spec', () => { - let staticConfig = { - cmpApi: 'static', - timeout: 7500, - consentData: { - getTCData: { + it('results in user settings overriding system defaults for v2 spec', () => { + const consentData = { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', 'cmpId': 92, 'cmpVersion': 100, @@ -218,18 +219,22 @@ describe('consentManagement', function () { 'legitimateInterests': {} } } - } - } - }; + }; - setConsentConfig(staticConfig); - expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(gdprScope).to.be.equal(false); - const consent = gdprDataHandler.getConsentData(); - expect(consent.consentString).to.eql(staticConfig.consentData.getTCData.tcString); - expect(consent.vendorData).to.eql(staticConfig.consentData.getTCData); - expect(staticConsentData).to.be.equal(staticConfig.consentData); + setConsentConfig({ + cmpApi: 'static', + timeout: 7500, + consentData: packageCfg(consentData) + }); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(gdprScope).to.be.equal(false); + const consent = gdprDataHandler.getConsentData(); + expect(consent.consentString).to.eql(consentData.tcString); + expect(consent.vendorData).to.eql(consentData); + expect(staticConsentData).to.be.equal(consentData); + }); + }); }); }); }); @@ -243,9 +248,7 @@ describe('consentManagement', function () { const staticConfig = { cmpApi: 'static', timeout: 7500, - consentData: { - getTCData: {} - } + consentData: {} } let didHookReturn; diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index c63dc8f9c3b..261414f336d 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -389,6 +389,14 @@ describe('Conversant adapter tests', function() { expect(payload.device).to.have.property('ua', navigator.userAgent); expect(payload).to.not.have.property('user'); // there should be no user by default + expect(payload).to.not.have.property('tmax'); // there should be no user by default + }); + + it('Verify timeout', () => { + const bidderRequest = { timeout: 9999 }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.tmax).equals(bidderRequest.timeout); }); it('Verify first party data', () => { diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 943b7cc0162..89485adf28b 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import parse from 'url-parse'; -import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo.js'; +import {buildDfpVideoUrl, buildAdpodVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; import adUnit from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -13,6 +13,7 @@ import { server } from 'test/mocks/xhr.js'; import * as adServer from 'src/adserver.js'; import {deepClone} from 'src/utils.js'; import {hook} from '../../../src/hook.js'; +import {getRefererInfo} from '../../../src/refererDetection.js'; const bid = { videoCacheKey: 'abc', @@ -27,6 +28,40 @@ describe('The DFP video support module', function () { hook.ready(); }); + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + Object.entries({ + params: { + params: { + 'iu': 'my/adUnit' + } + }, + url: { + url: 'https://some-example-url.com' + } + }).forEach(([t, options]) => { + describe(`when using ${t}`, () => { + it('should use page location as default for description_url', () => { + sandbox.stub(dep, 'ri').callsFake(() => ({page: 'example.com'})); + + const url = parse(buildDfpVideoUrl(Object.assign({ + adUnit: adUnit, + bid: bid, + }, options))); + const prm = utils.parseQS(url.query); + expect(prm.description_url).to.eql('example.com'); + }) + }) + }) + it('should make a legal request URL when given the required params', function () { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -63,9 +98,6 @@ describe('The DFP video support module', function () { })); expect(url.host).to.equal('video.adserver.example'); - - const queryObject = utils.parseQS(url.query); - expect(queryObject.description_url).to.equal('vastUrl.example'); }); it('requires a params object or url', function () { diff --git a/test/spec/modules/fledge_spec.js b/test/spec/modules/fledge_spec.js index 2094ab42438..a81ff05596e 100644 --- a/test/spec/modules/fledge_spec.js +++ b/test/spec/modules/fledge_spec.js @@ -2,6 +2,13 @@ import { expect } from 'chai'; import * as fledge from 'modules/fledgeForGpt.js'; +import {config} from '../../../src/config.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as utils from '../../../src/utils.js'; +import {hook} from '../../../src/hook.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; const CODE = 'sampleBidder'; const AD_UNIT_CODE = 'mock/placement'; @@ -29,9 +36,173 @@ describe('fledgeForGpt module', function() { nextFnSpy = sinon.spy(); }); - it('should call next() when a proper bidrequest and fledgeAuctionConfig are provided', function() { - fledge.addComponentAuctionHook(nextFnSpy, bidRequest, fledgeAuctionConfig); + it('should call next() when a proper adUnitCode and fledgeAuctionConfig are provided', function() { + fledge.addComponentAuctionHook(nextFnSpy, bidRequest.adUnitCode, fledgeAuctionConfig); expect(nextFnSpy.called).to.be.true; }); }); }); + +describe('fledgeEnabled', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])) + + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { navigator[p] = sinon.stub() }); + hook.ready(); + }); + + after(function() { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }) + + afterEach(function () { + config.resetConfig(); + }); + + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({bidderSequence: 'fixed'}) + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + fledgeEnabled: true, + } + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + }); +}); + +describe('ortb processors for fledge', () => { + describe('imp.ext.ae', () => { + it('should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + }) + it('should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: false}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(false); + }); + }); + describe('parseExtPrebidFledge', () => { + function packageConfigs(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + } + } + + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + function extractResult(ctx) { + return Object.fromEntries( + Object.entries(ctx) + .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .filter(([_, val]) => val != null) + ); + } + + it('should collect fledge configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({}); + }); + }); + describe('setResponseFledgeConfigs', () => { + it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + const ctx = { + impContext: { + 1: { + bidRequest: {bidId: 'bid1'}, + fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + }, + 2: { + bidRequest: {bidId: 'bid2'}, + fledgeConfigs: [{config: {id: 3}}] + }, + 3: { + bidRequest: {bidId: 'bid3'} + } + } + }; + const resp = {}; + setResponseFledgeConfigs(resp, {}, ctx); + expect(resp.fledgeAuctionConfigs).to.eql([ + {bidId: 'bid1', config: {id: 1}}, + {bidId: 'bid1', config: {id: 2}}, + {bidId: 'bid2', config: {id: 3}}, + ]); + }); + it('should not set fledgeAuctionConfigs if none exist', () => { + const resp = {}; + setResponseFledgeConfigs(resp, {}, { + impContext: { + 1: { + fledgeConfigs: [] + }, + 2: {} + } + }); + expect(resp).to.eql({}); + }); + }); +}); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 92a4b42f6f8..e9681c05314 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -54,6 +54,16 @@ describe('fluctAdapter', function () { }); describe('buildRequests', function () { + let sb; + + beforeEach(function () { + sb = sinon.sandbox.create(); + }); + + afterEach(function () { + sb.restore(); + }); + const bidRequests = [{ bidder: 'fluct', params: { @@ -70,7 +80,7 @@ describe('fluctAdapter', function () { }]; const bidderRequest = { refererInfo: { - referer: 'http://example.com' + page: 'http://example.com' } }; @@ -84,6 +94,11 @@ describe('fluctAdapter', function () { expect(request.url).to.equal('https://hb.adingo.jp/prebid?dfpUnitCode=%2F100000%2Funit_code&tagId=10000%3A100000001&groupId=1000000002'); }); + it('includes data.page by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.page).to.eql('http://example.com'); + }); + it('includes data.user.eids = [] by default', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.user.eids).to.eql([]); @@ -99,6 +114,11 @@ describe('fluctAdapter', function () { expect(request.data.schain).to.eql(undefined); }); + it('includes no data.regs by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs).to.eql(undefined); + }); + it('includes filtered user.eids if any exist', function () { const bidRequests2 = bidRequests.map( (bidReq) => Object.assign(bidReq, { @@ -211,6 +231,44 @@ describe('fluctAdapter', function () { ] }); }); + + it('includes data.regs.gdpr if bidderRequest.gdprConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'gdpr-consent-string', + gdprApplies: true, + }, + }), + )[0]; + expect(request.data.regs.gdpr).to.eql({ + consent: 'gdpr-consent-string', + gdprApplies: 1, + }); + }); + + it('includes data.regs.us_privacy if bidderRequest.uspConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + uspConsent: 'usp-consent-string', + }), + )[0]; + expect(request.data.regs.us_privacy).to.eql({ + consent: 'usp-consent-string', + }); + }); + + it('includes data.regs.coppa if config.getConfig("coppa") is true', function () { + const cfg = { + coppa: true, + }; + sb.stub(config, 'getConfig').callsFake(key => cfg[key]); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs.coppa).to.eql(1); + }); }); describe('interpretResponse', function() { @@ -247,9 +305,15 @@ describe('fluctAdapter', function () { adm: '<!-- test creative -->', burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', crid: 'test_creative', - adomain: ['test_adomain'] + adomain: ['test_adomain'], }] - }] + }], + usersyncs: [ + { + 'type': 'image', + 'url': 'https://cs.adingo.jp/sync', + }, + ], } }; @@ -338,4 +402,71 @@ describe('fluctAdapter', function () { expect(spec.interpretResponse({})).to.be.empty; }); }); + + describe('getUserSyncs', function () { + const syncOptions = {}; + const serverResponse = { + body: { + usersyncs: [ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ], + }, + }; + + it('returns no user syncs if syncOption.pixelEnabled !== true and syncOption.iframeEnabled !== true', function () { + const actual = spec.getUserSyncs( + syncOptions, + [serverResponse], + ); + + expect(actual).to.eql([]); + }); + + it('returns user syncs if syncOption.pixelEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + pixelEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + ]); + }); + + it('returns user syncs if syncOption.iframeEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + iframeEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ]); + }); + }); }); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 4d0c87ace3a..fe07dc1e719 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -1,8 +1,10 @@ import { expect } from 'chai'; import { spec } from 'modules/freewheel-sspBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { createEidsArray } from 'modules/userId/eids.js'; const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; +const PREBID_VERSION = '$prebid.version$'; describe('freewheelSSP BidAdapter Test', () => { const adapter = newBidder(spec); @@ -131,6 +133,22 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload._fw_bidfloorcur).to.deep.equal('USD'); }); + it('should pass 3rd party IDs with the request when present', function () { + const bidRequest = bidRequests[0]; + bidRequest.userIdAsEids = createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + admixerId: 'admixerId_FROM_USER_ID_MODULE', + adtelligentId: 'adtelligentId_FROM_USER_ID_MODULE' + }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_prebid_3p_UID).to.deep.equal(JSON.stringify([ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'admixer.net', uids: [{id: 'admixerId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'adtelligent.com', uids: [{id: 'adtelligentId_FROM_USER_ID_MODULE', atype: 3}]}, + ])); + }); + it('should return empty bidFloorCurrency when bidfloor <= 0', () => { const bidRequest = bidRequests[0]; bidRequest.getFloor = () => ({ currency: 'USD', floor: -1 }); @@ -149,6 +167,7 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); expect(payload.playerSize).to.equal('300x600'); + expect(payload.pbjs_version).to.equal(PREBID_VERSION); }); it('should return a properly formatted request with schain defined', function () { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index d411f33ab50..7ff8094a80c 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -575,6 +575,30 @@ describe('TheMediaGrid Adapter', function () { expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); }); + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + const gppBidderRequest = Object.assign({gppConsent: {gppString: consentString, applicableSections: [8]}}, bidderRequest); + + const [request] = spec.buildRequests(bidRequests, gppBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.regs).to.exist; + expect(payload.regs.gpp).to.equal(consentString); + expect(payload.regs.gpp_sid).to.deep.equal([8]); + }); + + it('should add gpp information to the request via bidderRequest.ortb2.regs.gpp', function () { + let consentString = 'abc1234'; + const gppBidderRequest = Object.assign({ortb2: {regs: {gpp: consentString, gpp_sid: [8]}}}, bidderRequest); + + const [request] = spec.buildRequests(bidRequests, gppBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.regs).to.exist; + expect(payload.regs.gpp).to.equal(consentString); + expect(payload.regs.gpp_sid).to.deep.equal([8]); + }); + it('if userId is present payload must have user.ext param with right keys', function () { const eids = [ { @@ -667,14 +691,6 @@ describe('TheMediaGrid Adapter', function () { const [request] = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); @@ -793,7 +809,7 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content.data).to.deep.equal(contentData); }); - it('should have right value in user.data when jwpsegments are present', function () { + it('should have right value in user.data', function () { const userData = [ { name: 'someName', @@ -823,13 +839,7 @@ describe('TheMediaGrid Adapter', function () { }); const [request] = spec.buildRequests([bidRequestsWithJwTargeting], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }, ...userData]); + expect(payload.user.data).to.deep.equal(userData); }); it('should have site.content.id filled from config ortb2.site.content.id', function () { diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index b400ef3394d..e4f06a451d2 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -245,14 +245,6 @@ describe('TheMediaGridNM Adapter', function () { requests.forEach((req, i) => { const payload = req.data; expect(req).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 80ca85801b6..71f356a83ae 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -491,7 +491,7 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent).to.exist; expect(bidRequest.data.gppConsent.gppString).to.equal(gppConsent.gppString); - expect(bidRequest.data.gppConsent.applicableSections).to.equal(gppConsent.applicableSections); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(gppConsent.applicableSections); expect(bidRequest.data.gppConsent.gppString).to.eq('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); }); it('should handle ortb2 parameters', function () { @@ -511,7 +511,7 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent).to.exist; expect(bidRequest.data.gppConsent.gppString).to.equal(undefined); - expect(bidRequest.data.gppConsent.applicableSections).to.equal(undefined); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(undefined); }); it('should handle ortb2 undefined parameters', function () { const ortb2 = { @@ -523,7 +523,7 @@ describe('gumgumAdapter', function () { const fakeBidRequest = { gppConsent: ortb2 }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent.gppString).to.eq(undefined) - expect(bidRequest.data.gppConsent.applicableSections).to.eq(undefined) + expect(bidRequest.data.gppConsent.gpp_sid).to.eq(undefined) }); it('should not set coppa parameter if coppa config is set to false', function () { config.setConfig({ diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 400b9145e0b..3b951c6e045 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,9 +1,9 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import {CONVERTER, spec} from 'modules/improvedigitalBidAdapter.js'; -import { config } from 'src/config.js'; -import { deepClone } from 'src/utils.js'; +import {config} from 'src/config.js'; +import {deepClone} from 'src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; -import { deepSetValue } from '../../../src/utils'; +import {deepSetValue} from '../../../src/utils'; // load modules that register ORTB processors import 'src/prebid.js'; import 'modules/currency.js'; @@ -13,7 +13,7 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -import {decorateAdUnitsWithNativeParams, toLegacyResponse} from '../../../src/native.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -278,12 +278,14 @@ describe('Improve Digital Adapter Tests', function () { placementId: 1053688, } }, - video: { - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, + ...(FEATURES.VIDEO && { + video: { + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + } + }), banner: { format: [ {w: 300, h: 250}, @@ -468,102 +470,104 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].video).to.not.exist; }); - it('should add correct placement value for instream and outstream video', function () { - let bidRequest = deepClone(simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video).to.not.exist; + if (FEATURES.VIDEO) { + it('should add correct placement value for instream and outstream video', function () { + let bidRequest = deepClone(simpleBidRequest); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video).to.not.exist; - bidRequest = deepClone(simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(1); - bidRequest.mediaTypes.video.context = 'outstream'; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(3); - }); + bidRequest = deepClone(simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(1); + bidRequest.mediaTypes.video.context = 'outstream'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(3); + }); - it('should set video params for instream', function() { - const bidRequest = deepClone(instreamBidRequest); - delete bidRequest.mediaTypes.video.playerSize; - const videoParams = { - mimes: ['video/mp4'], - skip: 1, - skipmin: 5, - skipafter: 30, - minduration: 15, - maxduration: 60, - startdelay: 5, - minbitrate: 500, - maxbitrate: 2000, - w: 1024, - h: 640, - placement: INSTREAM_TYPE, - }; - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal(videoParams); - }); + it('should set video params for instream', function() { + const bidRequest = deepClone(instreamBidRequest); + delete bidRequest.mediaTypes.video.playerSize; + const videoParams = { + mimes: ['video/mp4'], + skip: 1, + skipmin: 5, + skipafter: 30, + minduration: 15, + maxduration: 60, + startdelay: 5, + minbitrate: 500, + maxbitrate: 2000, + w: 1024, + h: 640, + placement: INSTREAM_TYPE, + }; + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal(videoParams); + }); - it('should set video playerSize over video params', () => { - const bidRequest = deepClone(instreamBidRequest); - bidRequest.params.video = { - w: 1024, h: 640 - } - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video.h).equal(480); - expect(payload.imp[0].video.w).equal(640); - }); + it('should set video playerSize over video params', () => { + const bidRequest = deepClone(instreamBidRequest); + bidRequest.params.video = { + w: 1024, h: 640 + } + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.h).equal(480); + expect(payload.imp[0].video.w).equal(640); + }); - it('should ignore invalid/unexpected video params', function() { - const bidRequest = deepClone(instreamBidRequest); - // 1 - const videoTest = { - skip: 1, - skipmin: 5, - skipafter: 30 - } - const videoTestInvParam = Object.assign({}, videoTest); - videoTestInvParam.blah = 1; - bidRequest.params.video = videoTestInvParam; - let request = spec.buildRequests([bidRequest], {})[0]; - let payload = JSON.parse(request.data); - expect(payload.imp[0].video.blah).not.to.exist; - }); + it('should ignore invalid/unexpected video params', function() { + const bidRequest = deepClone(instreamBidRequest); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const videoTestInvParam = Object.assign({}, videoTest); + videoTestInvParam.blah = 1; + bidRequest.params.video = videoTestInvParam; + let request = spec.buildRequests([bidRequest], {})[0]; + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.blah).not.to.exist; + }); - it('should set video params for outstream', function() { - const bidRequest = deepClone(outstreamBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal({...{ - mimes: ['video/mp4'], - placement: OUTSTREAM_TYPE, - w: bidRequest.mediaTypes.video.playerSize[0], - h: bidRequest.mediaTypes.video.playerSize[1], - }, - ...videoParams}); - }); - // - it('should set video params for multi-format', function() { - const bidRequest = deepClone(multiFormatBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - const testVideoParams = Object.assign({ - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, videoParams); - expect(payload.imp[0].video).to.deep.equal(testVideoParams); - }); + it('should set video params for outstream', function() { + const bidRequest = deepClone(outstreamBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal({...{ + mimes: ['video/mp4'], + placement: OUTSTREAM_TYPE, + w: bidRequest.mediaTypes.video.playerSize[0], + h: bidRequest.mediaTypes.video.playerSize[1], + }, + ...videoParams}); + }); + // + it('should set video params for multi-format', function() { + const bidRequest = deepClone(multiFormatBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + const testVideoParams = Object.assign({ + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, videoParams); + expect(payload.imp[0].video).to.deep.equal(testVideoParams); + }); + } it('should add schain', function () { const schain = '{"ver":"1.0","complete":1,"nodes":[{"asi":"headerlift.com","sid":"xyz","hp":1}]}'; @@ -609,7 +613,7 @@ describe('Improve Digital Adapter Tests', function () { const request = JSON.parse(requests[0].data); expect(request.imp.length).to.equal(2); expect(request.imp[0].banner).to.exist; - expect(request.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(request.imp[1].video).to.exist; } }); it('should create one request per endpoint in a single request mode', function () { @@ -623,7 +627,7 @@ describe('Improve Digital Adapter Tests', function () { const adServerRequest = JSON.parse(requests[1].data); expect(adServerRequest.imp.length).to.equal(2); expect(adServerRequest.imp[0].banner).to.exist; - expect(adServerRequest.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(adServerRequest.imp[1].video).to.exist; } }); it('should set Prebid sizes in bid request', function () { @@ -670,7 +674,7 @@ describe('Improve Digital Adapter Tests', function () { it('should not set site when app is defined in CONFIG', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; @@ -709,7 +713,7 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); getConfigStub.withArgs('site').returns({}); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.exist; expect(payload.app).does.not.exist; @@ -1238,32 +1242,34 @@ describe('Improve Digital Adapter Tests', function () { } // Video - it('should return a well-formed instream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); - expectMatch(bids, expectedBidInstreamVideo); - }); + if (FEATURES.VIDEO) { + it('should return a well-formed instream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); + expectMatch(bids, expectedBidInstreamVideo); + }); - it('should return a well-formed outstream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); - }); + it('should return a well-formed outstream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); + }); - it('should return a well-formed outstream video bid for multi-format ad unit', function () { - const request = makeRequest(multiFormatBidderRequest); - const videoResponse = deepClone(serverResponseVideo); - let bids = spec.interpretResponse(videoResponse, request); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); + it('should return a well-formed outstream video bid for multi-format ad unit', function () { + const request = makeRequest(multiFormatBidderRequest); + const videoResponse = deepClone(serverResponseVideo); + let bids = spec.interpretResponse(videoResponse, request); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); - videoResponse.body.seatbid[0].bid[0].adm = '<vAst'; - bids = spec.interpretResponse(videoResponse, request); - expect(bids[0].mediaType).to.equal(VIDEO); + videoResponse.body.seatbid[0].bid[0].adm = '<vAst'; + bids = spec.interpretResponse(videoResponse, request); + expect(bids[0].mediaType).to.equal(VIDEO); - videoResponse.body.seatbid[0].bid[0].adm = '<?xml'; - bids = spec.interpretResponse(videoResponse, request); - expect(bids[0].mediaType).to.equal(VIDEO); - }); + videoResponse.body.seatbid[0].bid[0].adm = '<?xml'; + bids = spec.interpretResponse(videoResponse, request); + expect(bids[0].mediaType).to.equal(VIDEO); + }); + } }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 5f104dce13f..d0b2d7344b5 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3469,7 +3469,7 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); - it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + it('should contain `consented_providers_settings.addtl_consent` & consent on user.ext when both are provided', function () { const options = { gdprConsent: { consentString: '3huaa11=qu3198ae', @@ -3479,11 +3479,11 @@ describe('IndexexchangeAdapter', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithConsent = extractPayload(validBidWithConsent[0]); - expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consented_providers_settings.addtl_consent).to.equal('1~1.35.41.101'); expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); }); - it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + it('should not contain `consented_providers_settings.addtl_consent` on user.ext when consent is not provided', function () { const options = { gdprConsent: { addtlConsent: '1~1.35.41.101', @@ -3492,7 +3492,7 @@ describe('IndexexchangeAdapter', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithConsent = extractPayload(validBidWithConsent[0]); - expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings.addtl_consent')).to.not.exist; expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; }); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 525c00d655c..556e06268b1 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -325,6 +325,7 @@ describe('kargo adapter tests', function () { usp: '1---' }, pageURL: 'https://www.prebid.org', + prebidVersion: '$prebid.version$', prebidRawBidRequests: [ { bidId: 1, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 7c0d8e92001..2f48fd6df8c 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -98,6 +98,24 @@ const BIDDER_REQUEST = { 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } } } }; @@ -263,6 +281,22 @@ describe('KueezRtbBidAdapter', function () { res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -310,6 +344,22 @@ describe('KueezRtbBidAdapter', function () { bidderTimeout: 3000, bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/kulturemediaBidAdapter_spec.js b/test/spec/modules/kulturemediaBidAdapter_spec.js new file mode 100644 index 00000000000..1872f6c171a --- /dev/null +++ b/test/spec/modules/kulturemediaBidAdapter_spec.js @@ -0,0 +1,613 @@ +import {expect} from 'chai'; +import {spec} from 'modules/kulturemediaBidAdapter.js'; + +const BANNER_REQUEST = { + 'bidderCode': 'kulturemedia', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'kulturemedia', + 'params': { + 'placementId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'kulturemedia', + 'params': { + 'placementId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'responseId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '<script>adm</script>', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '<script>adm2</script>', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'kulturemedia' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +const DEFAULT_NETWORK_ID = 1; + +describe('kulturemediaBidAdapter:', function () { + let videoBidRequest; + + const VIDEO_REQUEST = { + 'bidderCode': 'kulturemedia', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '34feaad34lkj2', + 'bids': videoBidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; + + beforeEach(function () { + videoBidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'kulturemedia', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + publisherId: 'km123' + } + }; + }); + + describe('isBidRequestValid', function () { + context('basic validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should accept request if placementId and publisherId are passed', function () { + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + + it('reject requests without params', function () { + this.bid.params = {}; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when banner mediaType does not exist', function () { + this.bid.mediaTypes = {} + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + context('banner validation', function () { + it('returns true when banner sizes are defined', function () { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false when banner sizes are invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((sizes) => { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + }); + + context('video validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'instream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should return true (skip validations) when e2etest = true', function () { + this.bid.params = { + e2etest: true + }; + expect(spec.isBidRequestValid(this.bid)).to.equal(true); + }); + + it('returns false when video context is not defined', function () { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + }); + }); + + describe('buildRequests', function () { + context('when mediaType is banner', function () { + it('creates request data', function () { + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, BANNER_REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', BANNER_REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', BANNER_REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, BANNER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('should properly forward eids parameters', function () { + const req = Object.assign({}, BANNER_REQUEST); + req.bidRequest[0].userIdAsEids = [ + { + source: 'dummy.com', + uids: [ + { + id: 'd6d0a86c-20c6-4410-a47b-5cba383a698a', + atype: 1 + } + ] + }]; + let request = spec.buildRequests(req.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext.eids[0].source).to.equal('dummy.com'); + expect(payload.user.ext.eids[0].uids[0].id).to.equal('d6d0a86c-20c6-4410-a47b-5cba383a698a'); + expect(payload.user.ext.eids[0].uids[0].atype).to.equal(1); + }); + }); + + context('when mediaType is video', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url.trim()).to.equal(spec.ENDPOINT + '?pid=' + videoBidRequest.params.publisherId + '&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach request data', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + const [width, height] = videoBidRequest.sizes; + const VERSION = '1.0.0'; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); + + it('should set pubId to e2etest when bid.params.e2etest = true', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url).to.equal(spec.ENDPOINT + '?pid=e2etest&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach End 2 End test data', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + expect(data.imp[0].bidfloor).to.not.exist; + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + }); + }); + }); + + describe('interpretResponse', function () { + context('when mediaType is banner', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, BANNER_REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index].meta).to.have.property('advertiserDomains', RESPONSE.body.seatbid[0].bid[index].adomain); + expect(bids[index]).to.have.property('ttl', 300); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, BANNER_REQUEST); + + expect(bids).to.be.empty; + }); + }); + + context('when mediaType is video', function () { + it('should return no bids if the response is not valid', function () { + const bidResponse = spec.interpretResponse({ + body: null + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + price: 6.01 + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + adm: '<VAST></VAST>' + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response with just "adm"', function () { + const serverResponse = { + id: '123', + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + impid: 456, + crid: 2, + price: 6.01, + adm: '<VAST></VAST>', + adomain: [ + 'kulturemedia.com' + ], + w: 640, + h: 480, + ext: { + prebid: { + type: 'video' + }, + } + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + let o = { + requestId: serverResponse.seatbid[0].bid[0].impid, + ad: '<VAST></VAST>', + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].crid, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + advertiserDomains: ['kulturemedia.com'] + } + }; + expect(bidResponse[0]).to.deep.equal(o); + }); + + it('should default ttl to 300', function () { + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '<VAST></VAST>'}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + videoBidRequest.params.video.ttl = 3601; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '<VAST></VAST>'}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + videoBidRequest.params.video.ttl = 0; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '<VAST></VAST>'}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}) +; diff --git a/test/spec/modules/lemmaDigitalBidAdapter_spec.js b/test/spec/modules/lemmaDigitalBidAdapter_spec.js new file mode 100644 index 00000000000..d50728dce3c --- /dev/null +++ b/test/spec/modules/lemmaDigitalBidAdapter_spec.js @@ -0,0 +1,623 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lemmaDigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +const constants = require('src/constants.json'); + +describe('lemmaDigitalBidAdapter', function () { + let bidRequests; + let videoBidRequests; + let bidResponses; + let videoBidResponse; + let schainConfig; + beforeEach(function () { + schainConfig = { + 'complete': 0, + 'nodes': [ + { + 'asi': 'mobupps.com', + 'sid': 'c74d97b01eae257e44aa9d5bade97baf5149', + 'rid': '79c25703ad5935b0b23b66d210dad1f3', + 'hp': 1 + }, + { + 'asi': 'lemmatechnologies.com', + 'sid': '975', + 'rid': 'a455157a-a1fb-11ed-a0e4-d08e79f7ace0', + 'hp': 1 + } + ] + }; + bidRequests = [{ + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD', + bidFloor: 1.3, + geo: { + lat: '12.3', + lon: '23.7', + }, + banner: { + w: 300, + h: 250, + }, + tmax: 300, + bcat: ['IAB-26'] + }, + sizes: [ + [300, 250], + [300, 600] + ], + schain: schainConfig + }]; + videoBidRequests = [{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + params: { + pubId: 1001, + adunitId: 1, + bidFloor: 1.3, + tmax: 300, + bcat: ['IAB-26'], + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + schain: schainConfig + }]; + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '<div style="background-color:#3399ff;"><p style="color:#ff0000;font-size:30px;font-family:Courier, monospace;">lemma<span style="color:white;font-style:italic;font-size:20px;">"Connecting Advertisers and Publishers directly"</span></p></div><script src="https://track.lemmahq.com/event?data=%7B%22id%22%3A%2216c8a49b-3f40-4000-870c-0c34c7fc8d00%22%2C%22bid%22%3A%2282DAAE22-FF66-4FAB-84AB-347B0C5CD02C%22%2C%22ts%22%3A%2220190813092302%22%2C%22pid%22%3A1001%2C%22plcid%22%3A1%2C%22aid%22%3A1%2C%22did%22%3A1%2C%22cid%22%3A%2222918%22%2C%22af%22%3A2%2C%22at%22%3A1%2C%22w%22%3A300%2C%22h%22%3A250%2C%22crid%22%3A%22v55jutrh%22%2C%22pp%22%3A1.858309%2C%22cp%22%3A1.858309%2C%22mg%22%3A0%7D&type=1" type="text/javascript"></script>', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '<VAST version="3.0"><Ad id="601364"><InLine><AdSystem>Acudeo Compatible</AdSystem><AdTitle>VAST 2.0 Instream Test 1</AdTitle><Description>VAST 2.0 Instream Test 1</Description><Creatives><Creative AdID="601364"><Linear skipoffset="20% "><TrackingEvents><Tracking event="close"><![CDATA[https://mytracking.com/linear/close]]></Tracking><Tracking event="skip"><![CDATA[https://mytracking.com/linear/skip]]></Tracking><MediaFiles><MediaFile delivery="progressive" type="video/mp4" bitrate="500" width="400" height="300" scalable="true" maintainAspectRatio="true"><![CDATA[https://localhost/lemma.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + }); + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case', function () { + let isValid = spec.isBidRequestValid(); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId is not number', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: '301', + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: adunitId is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: video bid request mimes is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + validBid.params.video.mimes = []; + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + describe('Request formation', function () { + it('bidRequest check empty', function () { + let bidRequests = []; + let request = spec.buildRequests(bidRequests); + expect(request).to.equal(undefined); + }); + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + it('bidRequest imp array check empty', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + data.imp = []; + expect(data.imp.length).to.equal(0); + }); + it('Endpoint checking', function () { + let request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('https://bid.lemmadigital.com/lemma/servad?pid=1001&aid=1'); + expect(request.method).to.equal('POST'); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal('1'); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('Set sizes from mediaTypes object', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].sizes; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.sizes).to.equal(undefined); + }); + it('Check request banner object present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.banner).to.deep.equal(undefined); + }); + it('Check device, source object not present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].schain; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + delete data.device; + delete data.source; + expect(data.source).to.equal(undefined); + expect(data.device).to.equal(undefined); + }); + it('Set content from config, set site.content', function () { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set content from config, set app.content', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + }, + app: { + id: 'e0977d04e6bafece57b4b6e93314f10a', + name: 'AMC', + bundle: 'com.roku.amc', + storeurl: 'https://channelstore.roku.com/details/12716/amc', + cat: [ + 'IAB-26' + ], + publisher: { + 'id': '975' + } + }, + } + }]; + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.app.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set tmax from requestBids method', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.tmax).to.deep.equal(300); + }); + it('Request params check without mediaTypes object', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(600); // height + }); + it('Request params check: without tagId', function () { + delete bidRequests[0].params.adunitId; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + }); + it('Request params multi size format object check', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - size passed in adslot */ + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequests[0].sizes = [ + [300, 600], + [300, 250] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + /* case 3 - size passed in sizes but not in adslot */ + bidRequests[0].params.adunitId = 1; + bidRequests[0].sizes = [ + [300, 250], + [300, 600] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + it('Request params currency check', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bidRequests[0].params.currency + */ + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.source.ext.schain).to.deep.equal(videoBidRequests[0].schain); + }); + describe('setting imp.floor using floorModule', function () { + /* + Use the minimum value among floor from floorModule per mediaType + If params.bidFloor is set then take max(floor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function (req) { + return floorModuleTestData[req.mediaType]; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'AUD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'AUD', + 'floor': 2.00 + } + }; + newRequest = utils.deepClone(bidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function () { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function () { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function () { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('bidFloor is not passed, use minimum from floorModule', function () { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('bidFloor is passed as 1, use min of floorModule as it is highest', function () { + newRequest[0].params.bidFloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + it('should check for valid banner mediaType in request', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + + expect(response[0].mediaType).to.equal('banner'); + }); + it('should check for valid video mediaType in request', function () { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + + expect(response[0].mediaType).to.equal('video'); + }); + }); + }); + describe('Video request params', function () { + let sandbox, utilsMock, newVideoRequest; + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }); + + it('Video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; + let request = spec.buildRequests(newVideoRequest); + expect(request).to.equal(undefined); + }); + + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; + + let request = spec.buildRequests(newVideoRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); + }); + }); + describe('getUserSyncs', function () { + const syncurl_iframe = 'https://sync.lemmadigital.com/js/usersync.html?pid=1001'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + + it('execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + }]); + }); + + it('not execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal(undefined); + }); + }); + }); +}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3c22cda1154..af34c209a1d 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -5,7 +5,7 @@ import { server } from 'test/mocks/xhr.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; const responseHeader = {'Content-Type': 'application/json'} describe('LiveIntentId', function() { @@ -45,7 +45,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*/); const response = { unifiedId: 'a_unified_id', @@ -59,25 +59,31 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith(response)).to.be.true; }); - it('should fire an event when getId', function() { + it('should fire an event when getId', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, consentString: 'consentDataString' }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when getId and a hash is provided', function() { + it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + done(); + }, 200); }); - it('should initialize LiveConnect with the config params when decode and emit an event', function () { + it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, ...{ @@ -88,25 +94,34 @@ describe('LiveIntentId', function() { } } }}); - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + done(); + }, 200); }); - it('should initialize LiveConnect and emit an event with a privacy string when decode', function() { + it('should initialize LiveConnect and emit an event with a privacy string when decode', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: false, consentString: 'consentDataString' }) liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when decode and a hash is provided', function() { + it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + done(); + }, 200); }); it('should not return a decoded identifier when the unifiedId is not present in the value', function() { @@ -114,25 +129,31 @@ describe('LiveIntentId', function() { expect(result).to.be.eql({}); }); - it('should fire an event when decode', function() { + it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.be.not.null + setTimeout(() => { + expect(server.requests[0].url).to.be.not.null + done(); + }, 200); }); - it('should initialize LiveConnect and send data only once', function() { + it('should initialize LiveConnect and send data only once', function(done) { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests.length).to.be.eq(1); + setTimeout(() => { + expect(server.requests.length).to.be.eq(1); + done(); + }, 200); }); - it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); request.respond( 204, @@ -152,7 +173,7 @@ describe('LiveIntentId', function() { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); request.respond( 200, @@ -167,7 +188,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, @@ -182,7 +203,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 503, @@ -199,7 +220,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); request.respond( 200, @@ -222,7 +243,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, @@ -244,7 +265,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, @@ -277,7 +298,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); request.respond( 200, @@ -304,7 +325,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); request.respond( 200, diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index c408f23c4f4..e19c27cc2d3 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -30,9 +30,11 @@ const MOCK = { NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}], BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], + BIDS_SAME_REQ_DIFF_CPM_SAME_TIME: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}], - BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}] + BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}], + MULTI_BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'bidA2', 'originalBidder': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aebecc', 'originalRequestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 3.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 3.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '3.00', 'pbMg': '3.20', 'pbHg': '3.29', 'pbAg': '3.25', 'pbDg': '3.29', 'pbCg': '3.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '3.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}] }; function performAuctionWithFloorConfig() { @@ -102,6 +104,14 @@ function performStandardAuctionMultiBidResponseNoWin() { events.emit(SET_TARGETING, MOCK.SET_TARGETING); } +function performMultiBidAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + MOCK.MULTI_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { @@ -309,6 +319,13 @@ describe('Media.net Analytics Adapter', function() { expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); + it('should pick winning bid if multibids with same request id and same time to respond', function() { + performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME); + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + medianetAnalytics.clearlogsQueue(); + }); + it('should pick winning bid if multibids with same request id and equal cpm', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; @@ -348,5 +365,14 @@ describe('Media.net Analytics Adapter', function() { expect(errors.length).equals(1); expect(errors[0].event).equals(ERROR_WINNING_BID_ABSENT); }); + + it('can handle multi bid module', function () { + performMultiBidAuction(); + const queue = medianetAnalytics.getlogsQueue(); + expect(queue.length).equals(1); + const multiBidLog = queue.map((log) => getQueryData(log, true))[0]; + expect(multiBidLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet']); + expect(multiBidLog.status).to.have.ordered.members(['1', '1', '1']); + }) }); }); diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index de38554f010..000ddb2778d 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,9 +13,10 @@ import { getUniqueDealId, } from 'modules/minutemediaplusBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'exchange'; @@ -36,6 +37,10 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'mediaTypes': [BANNER] @@ -45,6 +50,10 @@ const VIDEO_BID = { 'bidId': '2d52001cabd527', 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { @@ -78,11 +87,37 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', 'ref': 'https://www.somereferrer.com' - } + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + }, }; const SERVER_RESPONSE = { @@ -134,7 +169,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -213,6 +248,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -223,17 +261,42 @@ describe('MinuteMediaPlus Bid Adapter', function () { bidFloor: 0.1, bidId: '2d52001cabd527', bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', cb: 1000, gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, publisherId: '59ac17c192832d0011283fe3', url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -259,6 +322,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -267,8 +333,33 @@ describe('MinuteMediaPlus Bid Adapter', function () { data: { gdprConsent: 'consent_string', gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -296,7 +387,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -305,7 +396,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -313,7 +404,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -329,12 +420,12 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -394,11 +485,11 @@ describe('MinuteMediaPlus Bid Adapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return { lipbid: id }; + return {lipbid: id}; case 'parrableId': - return { eid: id }; + return {eid: id}; case 'id5id': - return { uid: id }; + return {uid: id}; default: return id; } @@ -417,18 +508,18 @@ describe('MinuteMediaPlus Bid Adapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({ 'c_id': '1' }); - const pid = extractPID({ 'p_id': '1' }); - const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({ 'cID': '1' }); - const pid = extractPID({ 'Pid': '2' }); - const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -488,7 +579,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -504,8 +595,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7645ee59f63..9ba3ac0e5a4 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -1,9 +1,41 @@ -import {expect} from 'chai'; -import {spec} from 'modules/nexx360BidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; +import { expect } from 'chai'; +import { + spec, storage, getNexx360LocalStorage, +} from 'modules/nexx360BidAdapter.js'; +import { sandbox } from 'sinon'; + +const instreamResponse = { + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', + 'price': 5, + 'adomain': [ + 'appnexus.com' + ], + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'divId': 'video1', + 'adUnitCode': 'video1', + 'vastXml': '<VAST version="3.0">\n <Ad>\n <Wrapper>\n <AdSystem>Nexx360 Wrapper</AdSystem>\n <VASTAdTagURI><![CDATA[https://fast.nexx360.io/cache?uuid=f093f759-3143-4ad4-a52d-d2c6de420564]]></VASTAdTagURI>\n <Impression><![CDATA[https://fast.nexx360.io/track-imp?ssp=appnexus&type=booster&price=4.710315591144606&cur=EUR&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&consent=1&abtest_id=0&tag_id=29h5tilm&uuid=8ffb1d9e-3081-4855-87e9-8a9f5c251641&seat=9325&adomain=appnexus.com&mediatype=video]]></Impression>\n <Impression><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=impression]]></Impression>\n <Creatives>\n <Creative>\n <Linear>\n <TrackingEvents>\n <Tracking event="start"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=start]]></Tracking>\n <Tracking event="firstQuartile"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=firstQuartile]]></Tracking>\n <Tracking event="midpoint"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=midpoint]]></Tracking>\n <Tracking event="thirdQuartile"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=thirdQuartile]]></Tracking>\n <Tracking event="complete"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=complete]]></Tracking>\n <Tracking event="creativeView"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=creativeView]]></Tracking>\n </TrackingEvents>\n </Linear>\n </Creative>\n </Creatives>\n </Wrapper>\n </Ad>\n </VAST>' + } + } + ], + 'seat': 'appnexus' + } + ], + 'ext': { + 'cookies': [] + } +}; describe('Nexx360 bid adapter tests', function () { const DISPLAY_BID_REQUEST = { @@ -110,6 +142,32 @@ describe('Nexx360 bid adapter tests', function () { 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', } }); + + it('We verify isBidRequestValid with unvalid adUnitName', function() { + bannerBid.params = { adUnitName: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with empty adUnitName', function() { + bannerBid.params = { adUnitName: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid adUnitPath', function() { + bannerBid.params = { adUnitPath: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid divId', function() { + bannerBid.params = { divId: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid unvalid allBids', function() { + bannerBid.params = { allBids: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + it('We verify isBidRequestValid with uncorrect tagid', function() { bannerBid.params = { 'tagid': 'luvxjvgn' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); @@ -121,63 +179,87 @@ describe('Nexx360 bid adapter tests', function () { }); }); - describe('when request is for a multiformat ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const multiformatBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - }, - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); - }); + describe('getNexx360LocalStorage disabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => false); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) - describe('when request is for a video ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const videoBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); + describe('getNexx360LocalStorage enabled but nothing', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(typeof output.nexx360Id).to.be.eql('string'); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled but wrong payload', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(function () { + sandbox.restore() + }); + }) describe('buildRequests()', function() { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); + }); describe('We test with a multiple display bids', function() { const sampleBids = [ { bidder: 'nexx360', params: { - tagId: 'luvxjvgn' + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', }, - adUnitCode: 'div-1', + adUnitCode: 'header-ad-1234', transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', sizes: [[300, 250], [300, 600]], bidId: '44a2706ac3574', @@ -221,7 +303,7 @@ describe('Nexx360 bid adapter tests', function () { sizes: [[728, 90], [970, 250]] } }, - adUnitCode: 'div-2', + adUnitCode: 'div-2-abcd', transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', sizes: [[728, 90], [970, 250]], bidId: '5ba94555219a03', @@ -276,63 +358,83 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.cur[0]).to.be.eql('USD'); expect(requestContent.imp.length).to.be.eql(2); expect(requestContent.imp[0].id).to.be.eql('44a2706ac3574'); - expect(requestContent.imp[0].tagid).to.be.eql('div-1'); + expect(requestContent.imp[0].tagid).to.be.eql('header-ad'); expect(requestContent.imp[0].ext.divId).to.be.eql('div-1'); + expect(requestContent.imp[0].ext.adUnitCode).to.be.eql('header-ad-1234'); + expect(requestContent.imp[0].ext.adUnitName).to.be.eql('header-ad'); + expect(requestContent.imp[0].ext.adUnitPath).to.be.eql('/12345/nexx360/Homepage/HP/Header-Ad'); + expect(requestContent.imp[0].ext.dimensions.slotW).to.be.eql(200); + expect(requestContent.imp[0].ext.dimensions.slotH).to.be.eql(250); + expect(requestContent.imp[0].ext.dimensions.cssMaxW).to.be.eql('400px'); + expect(requestContent.imp[0].ext.dimensions.cssMaxH).to.be.eql('350px'); expect(requestContent.imp[0].ext.nexx360.tagId).to.be.eql('luvxjvgn'); - expect(requestContent.imp[0].ext.nexx360.allBids).to.be.eql(false); expect(requestContent.imp[0].banner.format.length).to.be.eql(2); expect(requestContent.imp[0].banner.format[0].w).to.be.eql(300); expect(requestContent.imp[0].banner.format[0].h).to.be.eql(250); expect(requestContent.imp[1].ext.nexx360.allBids).to.be.eql(true); - expect(requestContent.regs.ext.gdpr).to.be.eql(1); - expect(requestContent.user.ext.consent).to.be.eql(bidderRequest.gdprConsent.consentString); - expect(requestContent.user.ext.eids.length).to.be.eql(2); + expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); + expect(requestContent.ext.bidderVersion).to.be.eql('2.0'); + expect(requestContent.ext.source).to.be.eql('prebid.js'); }); - it('We perform a test with a multiformat adunit', function() { - const multiformatBids = [...sampleBids]; - multiformatBids[0].mediaTypes = { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6, 7, 8], - playbackmethod: [2], - skip: 1, - playback_method: ['auto_play_sound_off'] - } - }; - const request = spec.buildRequests(multiformatBids, bidderRequest); - const requestContent = request.data; - expect(requestContent.imp[0].video.context).to.be.eql('outstream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }); + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', function() { + const multiformatBids = [...sampleBids]; + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const requestContent = request.data; + expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); - it('We perform a test with a video adunit', function() { - const videoBids = [sampleBids[0]]; - videoBids[0].mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6], - playbackmethod: [2], - skip: 1 - } - }; - const request = spec.buildRequests(videoBids, bidderRequest); - const requestContent = request.data; - expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.imp[0].video.context).to.be.eql('instream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }) + it('We perform a test with a instream adunit', function() { + const videoBids = [sampleBids[0]]; + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }) + } + }); + after(function () { + sandbox.restore() }); }); describe('interpretResponse()', function() { + it('empty response', function() { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); it('banner responses', function() { const response = { body: { @@ -345,7 +447,6 @@ describe('Nexx360 bid adapter tests', function () { 'id': '4427551302944024629', 'impid': '226175918ebeda', 'price': 1.5, - 'type': 'banner', 'adomain': [ 'http://prebid.org' ], @@ -356,141 +457,153 @@ describe('Nexx360 bid adapter tests', function () { 'cat': [ 'IAB3-1' ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', 'ext': { - 'dsp_id': 'ssp1', - 'buyer_id': 'foo', - 'brand_id': 'bar' + 'adUnitCode': 'div-1', + 'mediaType': 'banner', + 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + 'ssp': 'appnexus', } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'id': 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + 'cookies': [] + }, } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].ext.adUrl); + expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].ext.mediaType); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); - expect(output[0].meta.networkId).to.be.eql(response.body.seatbid[0].bid[0].ext.dsp_id); - expect(output[0].meta.advertiserId).to.be.eql(response.body.seatbid[0].bid[0].ext.buyer_id); - expect(output[0].meta.brandId).to.be.eql(response.body.seatbid[0].bid[0].ext.brand_id); }); - it('video responses', function() { + it('instream responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', 'price': 5, - 'type': 'instream', 'adomain': [ - '' + 'appnexus.com' ], 'crid': '97517771', - 'ssp': 'appnexus', 'h': 1, 'w': 1, - 'vastXml': '<VAST version="3.0">\n <Ad>\n <Wrapper>\n <AdSystem>prebid.org wrapper</AdSystem>\n <VASTAdTagURI><![CDATA[https://fast.nexx360.io/cache?uuid=9987328a-b15a-4549-974b-203cd9bbe5d3]]></VASTAdTagURI>\n <Impression><![CDATA[https://fast.nexx360.io/track-imp?ssp=appnexus&type=booster&price=5.002000800320127&cur=EUR&user_agent=Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+13_2_3+like+Mac+OS+X%29+AppleWebKit%2F605.1.15+%28KHTML%2C+like+Gecko%29+Version%2F13.0.3+Mobile%2F15E148+Safari%2F604.1&consent=1&abtest_id=0&tag_id=yqsc1tfj&mediatype=video]]></Impression>\n <Creatives></Creatives>\n </Wrapper>\n </Ad>\n </VAST>' + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'adUnitCode': 'video1', + 'vastXml': '<VAST version="3.0">\n <Ad>\n <Wrapper>\n <AdSystem>Nexx360 Wrapper</AdSystem>\n <VASTAdTagURI><![CDATA[https://fast.nexx360.io/cache?uuid=f093f759-3143-4ad4-a52d-d2c6de420564]]></VASTAdTagURI>\n <Impression><![CDATA[https://fast.nexx360.io/track-imp?ssp=appnexus&type=booster&price=4.710315591144606&cur=EUR&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&consent=1&abtest_id=0&tag_id=29h5tilm&uuid=8ffb1d9e-3081-4855-87e9-8a9f5c251641&seat=9325&adomain=appnexus.com&mediatype=video]]></Impression>\n <Impression><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=impression]]></Impression>\n <Creatives>\n <Creative>\n <Linear>\n <TrackingEvents>\n <Tracking event="start"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=start]]></Tracking>\n <Tracking event="firstQuartile"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=firstQuartile]]></Tracking>\n <Tracking event="midpoint"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=midpoint]]></Tracking>\n <Tracking event="thirdQuartile"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=thirdQuartile]]></Tracking>\n <Tracking event="complete"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=complete]]></Tracking>\n <Tracking event="creativeView"><![CDATA[https://fast.nexx360.io/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=29h5tilm&uuid=c29446b7-3f15-4354-9ad2-9b7acea4b4b3&seat=9325&adomain=appnexus.com&event=creativeView]]></Tracking>\n </TrackingEvents>\n </Linear>\n </Creative>\n </Creatives>\n </Wrapper>\n </Ad>\n </VAST>' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - }); - describe('interpretResponse()', function() { - it('banner responses', function() { + it('outstream responses', function() { const response = { body: { - 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + 'id': '40c23932-135e-4602-9701-ca36f8d80c07', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '4427551302944024629', - 'impid': '226175918ebeda', - 'price': 1.5, - 'type': 'banner', + 'id': '1186971142548769361', + 'impid': '4ce809b61a3928', + 'price': 5, 'adomain': [ - 'http://prebid.org' - ], - 'crid': '98493581', - 'ssp': 'appnexus', - 'h': 600, - 'w': 300, - 'cat': [ - 'IAB3-1' + 'appnexus.com' ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493' + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'outstream', + 'ssp': 'appnexus', + 'adUnitCode': 'div-1', + 'vastXml': '<VAST version="3.0">\n <Ad>\n <Wrapper>\n <AdSystem>Nexx360 Wrapper</AdSystem>\n <VASTAdTagURI><![CDATA[http://localhost:8081/cache?uuid=7fcc7c63-3699-4544-a6d5-8ea33ee5bdcb]]></VASTAdTagURI>\n <Impression><![CDATA[http://localhost:8085/track-imp?ssp=appnexus&type=booster&price=4.710315591144606&cur=EUR&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&consent=1&abtest_id=0&tag_id=yqsc1tfj&uuid=d8fbebb6-f5d7-4ebd-b86b-8b1584c9445e&seat=9325&adomain=appnexus.com&mediatype=video]]></Impression>\n <Impression><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=impression]]></Impression>\n <Creatives>\n <Creative>\n <Linear>\n <TrackingEvents>\n <Tracking event="start"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=start]]></Tracking>\n <Tracking event="firstQuartile"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=firstQuartile]]></Tracking>\n <Tracking event="midpoint"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=midpoint]]></Tracking>\n <Tracking event="thirdQuartile"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=thirdQuartile]]></Tracking>\n <Tracking event="complete"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=complete]]></Tracking>\n <Tracking event="creativeView"><![CDATA[http://localhost:8085/track-vast?ssp=appnexus&type=booster&user_agent=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F110.0.0.0+Safari%2F537.36&abtest_id=0&tag_id=yqsc1tfj&uuid=415ad33f-3de1-4a30-bf9d-5751f3bed64d&seat=9325&adomain=appnexus.com&event=creativeView]]></Tracking>\n </TrackingEvents>\n </Linear>\n </Creative>\n </Creatives>\n </Wrapper>\n </Ad>\n </VAST>' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); + expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); + expect(typeof output[0].renderer).to.be.eql('object'); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - it('video responses', function() { + + it('native responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', - 'price': 5, - 'type': 'instream', + 'id': '6624930625245272225', + 'impid': '23e11d845514bb', + 'price': 10, 'adomain': [ - '' + 'prebid.org' ], - 'crid': '97517771', - 'ssp': 'appnexus', + 'crid': '97494204', 'h': 1, 'w': 1, - 'vastXml': '<VAST version="3.0">\n <Ad>\n <Wrapper>\n <AdSystem>prebid.org wrapper</AdSystem>\n <VASTAdTagURI><![CDATA[https://fast.nexx360.io/cache?uuid=9987328a-b15a-4549-974b-203cd9bbe5d3]]></VASTAdTagURI>\n <Impression><![CDATA[https://fast.nexx360.io/track-imp?ssp=appnexus&type=booster&price=5.002000800320127&cur=EUR&user_agent=Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+13_2_3+like+Mac+OS+X%29+AppleWebKit%2F605.1.15+%28KHTML%2C+like+Gecko%29+Version%2F13.0.3+Mobile%2F15E148+Safari%2F604.1&consent=1&abtest_id=0&tag_id=yqsc1tfj&mediatype=video]]></Impression>\n <Creatives></Creatives>\n </Wrapper>\n </Ad>\n </VAST>' + 'cat': [ + 'IAB3-1' + ], + 'ext': { + 'mediaType': 'native', + 'ssp': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1' + }, + 'adm': '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}' } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [], + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); - expect(output[0].mediaType).to.be.eql('video'); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + expect(output[0].native.ortb.ver).to.be.eql('1.2'); + expect(output[0].native.ortb.assets[0].id).to.be.eql(1); + expect(output[0].mediaType).to.be.eql('native'); }); }); @@ -501,7 +614,9 @@ describe('Nexx360 bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { - response.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); expect(syncs).to.have.lengthOf(1); expect(syncs[0]).to.have.property('type').and.to.equal('image'); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 7ea89f7dd3f..8328aae33d8 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -14,6 +14,38 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsWithFloor', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'getFloor': () => { return { currency: 'USD', floor: 1.00 } }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + } + ]; + + let bidderRequest = { + refererInfo: {page: REFERER} + } + + it('should FLoor = 1', function () { + spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest); + /* eslint-disable no-console */ + console.log('request.data:', request.data); + const payload = JSON.parse(request.data); + expect(payload.a[0].floor).to.equal(1); + }); + }); + describe('isBidRequestValid', function () { let bid = { 'bidder': 'nobid', diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 3bb1958c5fe..c1eeba62a29 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -991,12 +991,14 @@ describe('OpenxRtbAdapter', function () { }); }); - context('video', function () { - it('should send bid request with a mediaTypes specified with video type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); + if (FEATURES.VIDEO) { + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); + }); }); - }); + } it.skip('should send ad unit ids when any are defined', function () { const bidRequestsWithUnitIds = [{ @@ -1317,56 +1319,58 @@ describe('OpenxRtbAdapter', function () { }); }); - context('when the response is a video', function() { - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [[640, 360], [854, 480]], + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 854, - h: 480, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup', - }] - }], - cur: 'AUS' - }; - }); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); - it('should return the proper mediaType', function () { - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); - }); + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); - it('should return the proper mediaType', function () { - const winUrl = 'https//my.win.url'; - bidResponse.seatbid[0].bid[0].nurl = winUrl - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - expect(bid.vastUrl).to.equal(winUrl); + expect(bid.vastUrl).to.equal(winUrl); + }); }); - }); + } context('when the response contains FLEDGE interest groups config', function() { let response; diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..1f39d1a2cda 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -2,12 +2,15 @@ import { permutiveSubmodule, storage, getSegments, - initSegments, isAcEnabled, isPermutiveOnPage, setBidderRtb, getModuleConfig, PERMUTIVE_SUBMODULE_CONFIG_KEY, + readAndSetCohorts, + PERMUTIVE_STANDARD_KEYWORD, + PERMUTIVE_STANDARD_AUD_KEYWORD, + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' @@ -188,24 +191,81 @@ describe('permutiveRtdProvider', function () { const moduleConfig = getConfig() const bidderConfig = {}; const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { - expect(bidderConfig[bidder].user.data).to.deep.include.members([{ - name: 'permutive.com', - segment: expectedTargetingData - }]) + const customCohorts = segmentsData[bidder] || [] + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: 'permutive.com', + segment: expectedTargetingData, + }, + // Should have custom cohorts specific for that bidder + { + name: 'permutive', + segment: customCohorts.map(seg => { + return { id: seg } + }), + }, + ]) }) }) + + it('should override existing ortb2.user.data reserved by permutive RTD', function () { + const reservedPermutiveStandardName = 'permutive.com' + const reservedPermutiveCustomCohortName = 'permutive' + + const moduleConfig = getConfig() + const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + + const sampleOrtbConfig = { + user: { + data: [ + { + name: reservedPermutiveCustomCohortName, + segment: [{ id: 'remove-me' }, { id: 'remove-me-also' }] + }, + { + name: reservedPermutiveStandardName, + segment: [{ id: 'remove-me-also-also' }, { id: 'remove-me-also-also-also' }] + } + ] + } + } + + const bidderConfig = Object.fromEntries(acBidders.map(bidder => [bidder, sampleOrtbConfig])) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + + expect(bidderConfig[bidder].user.data).to.not.deep.include.members([...sampleOrtbConfig.user.data]) + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: reservedPermutiveCustomCohortName, + segment: customCohorts.map(id => ({ id })), + }, + { + name: reservedPermutiveStandardName, + segment: segmentsData.ac.map(id => ({ id })), + }, + ]) + }) + }) + it('should include ortb2 user data transformation for IAB audience taxonomy', function() { const moduleConfig = getConfig() const bidderConfig = {} const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) @@ -225,7 +285,7 @@ describe('permutiveRtdProvider', function () { } ) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].user.data).to.deep.include.members([ @@ -244,6 +304,8 @@ describe('permutiveRtdProvider', function () { it('should not overwrite ortb2 config', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -267,10 +329,7 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) @@ -280,6 +339,8 @@ describe('permutiveRtdProvider', function () { it('should update user.keywords and not override existing values', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -304,19 +365,31 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { + const customCohortsData = segmentsData[bidder] || [] + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentsData.ac, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: segmentsData.ssp.cohorts, + [PERMUTIVE_CUSTOM_COHORTS_KEYWORD]: customCohortsData + } + + // Transform groups of key-values into a single array of strings + // i.e { permutive: ['1', '2'], p_standard: ['3', '4'] } => ['permutive=1', 'permutive=2', 'p_standard=3',' p_standard=4'] + const transformedKeywordGroups = Object.entries(keywordGroups) + .flatMap(([keyword, ids]) => ids.map(id => `${keyword}=${id}`)) + + const keywords = `${sampleOrtbConfig.user.keywords},${transformedKeywordGroups.join(',')}` + expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]]) - expect(bidderConfig[bidder].user.keywords).to.deep.equal('a,b,p_standard_aud=123,p_standard_aud=abc') + expect(bidderConfig[bidder].user.keywords).to.deep.equal(keywords) }) }) it('should merge ortb2 correctly for ac and ssps', function () { - setLocalStorage({ + const customTargetingData = { + ...getTargetingData(), '_ppam': [], '_psegs': [], '_pcrprs': ['abc', 'def', 'xyz'], @@ -324,7 +397,10 @@ describe('permutiveRtdProvider', function () { ssps: ['foo', 'bar'], cohorts: ['xyz', 'uvw'], } - }) + } + const segmentsData = transformedTargeting(customTargetingData) + setLocalStorage(customTargetingData) + const moduleConfig = { name: 'permutive', waitForIt: true, @@ -335,7 +411,7 @@ describe('permutiveRtdProvider', function () { } const bidderConfig = {}; - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) // include both ac and ssp cohorts, as foo is both in ac bidders and ssps const expectedFooTargetingData = [ @@ -370,153 +446,137 @@ describe('permutiveRtdProvider', function () { segment: expectedOtherTargetingData }]) }) - }) - describe('Getting segments', function () { - it('should retrieve segments in the expected structure', function () { - const data = transformedTargeting() - expect(getSegments(250)).to.deep.equal(data) - }) - it('should enforce max segments', function () { - const max = 1 - const segments = getSegments(max) + describe('ortb2.user.ext tests', function () { + it('should add nothing if there are no cohorts data', function () { + // Empty module config means we default + const moduleConfig = getConfig() - for (const key in segments) { - if (key === 'ssp') { - expect(segments[key].cohorts).to.have.length(max) - } else { - expect(segments[key]).to.have.length(max) - } - } - }) - }) + const bidderConfig = {} - describe('Default segment targeting', function () { - it('sets segment targeting for Xandr', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - initSegments({ adUnits }, callback, config) + // Passing empty values means there is no segment data + const segmentsData = transformedTargeting({ + _pdfps: [], + _prubicons: [], + _papns: [], + _psegs: [], + _ppam: [], + _pcrprs: [], + _pssps: { ssps: [], cohorts: [] } + }) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + moduleConfig.params.acBidders.forEach(bidder => { + expect(bidderConfig[bidder].user).to.not.have.property('ext') }) - } - }) + }) - it('sets segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add standard and custom cohorts', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - it('sets segment targeting for Magnite video', function () { - const targetingData = getTargetingData() - targetingData._prubicons.push(321) + moduleConfig.params.acBidders.forEach(bidder => { + const userExtData = { + // Default targeting + p_standard: segmentsData.ac, + } - setLocalStorage(targetingData) + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + deepSetValue(userExtData, 'permutive', customCohorts) + } - const data = transformedTargeting(targetingData) - const config = getConfig() + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq(userExtData) + }) + }) - const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) - expect(adUnits).to.have.lengthOf(1) + it('should add ac cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback() { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + moduleConfig.params.acBidders.forEach((bidder) => { + // Remove custom cohorts + delete segmentsData[bidder] + }) - if (bidder === 'rubicon') { - expect( - deepAccess(params, 'visitor.permutive'), - 'Should map all targeting values to a string', - ).to.eql(data.rubicon.map(String)) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach((bidder) => { + expect(bidderConfig[bidder].user.ext.data).to.deep.equal({ + p_standard: segmentsData.ac }) }) - } - }) + }) - it('sets segment targeting for Ozone', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add custom cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + // Empty the AC cohorts + segmentsData['ac'] = [] - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ permutive: customCohorts }) + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } }) - } + }) }) }) - describe('Custom segment targeting', function () { - it('sets custom segment targeting for Magnite', function () { + describe('Getting segments', function () { + it('should retrieve segments in the expected structure', function () { const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + expect(getSegments(250)).to.deep.equal(data) + }) + it('should enforce max segments', function () { + const max = 1 + const segments = getSegments(max) - config.params.overwrites = { - rubicon: function (bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } + for (const key in segments) { + if (key === 'ssp') { + expect(segments[key].cohorts).to.have.length(max) + } else { + expect(segments[key]).to.have.length(max) } } + }) + }) - initSegments({ adUnits }, callback, config) + describe('Default segment targeting', function () { + it('sets segment targeting for Ozone', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + readAndSetCohorts({ adUnits }, config) - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } }) - } + }) }) }) @@ -525,73 +585,65 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Magnite', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Ozone', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for TrustX', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'trustx') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) }) @@ -648,6 +700,7 @@ function transformedTargeting (data = getTargetingData()) { return { ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], appnexus: data._papns, + ix: data._pindexs, rubicon: data._prubicons, gam: data._pdfps, ssp: data._pssps, @@ -661,6 +714,7 @@ function getTargetingData () { _papns: ['appnexus1', 'appnexus2'], _psegs: ['1234', '1000001', '1000002'], _ppam: ['ppam1', 'ppam2'], + _pindexs: ['pindex1', 'pindex2'], _pcrprs: ['pcrprs1', 'pcrprs2', 'dup'], _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 820a57b4e83..9516f0402c1 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {deepAccess, deepClone} from 'src/utils.js'; +import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; @@ -25,13 +25,15 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/fledgeForGpt.js'; import {hook} from '../../../src/hook.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {registerBidder} from 'src/adapters/bidderFactory.js'; +import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {syncAddFPDEnrichments, syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {deepSetValue} from '../../../src/utils.js'; let CONFIG = { accountId: '1', @@ -706,51 +708,53 @@ describe('S2S Adapter', function () { expect(server.requests.length).to.equal(0); }); - it('should add outstream bc renderer exists on mediatype', function () { - config.setConfig({ s2sConfig: CONFIG }); + if (FEATURES.VIDEO) { + it('should add outstream bc renderer exists on mediatype', function () { + config.setConfig({ s2sConfig: CONFIG }); - adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.exist; - expect(requestBid.imp[0].video).to.exist; - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.exist; + expect(requestBid.imp[0].video).to.exist; + }); - it('should default video placement if not defined and instream', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('should default video placement if not defined and instream', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: ortb2Config }); - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + }); - it('converts video mediaType properties into openRTB format', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('converts video mediaType properties into openRTB format', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: ortb2Config }); - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - expect(requestBid.imp[0].video.w).to.equal(640); - expect(requestBid.imp[0].video.h).to.equal(480); - expect(requestBid.imp[0].video.playerSize).to.be.undefined; - expect(requestBid.imp[0].video.context).to.be.undefined; - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + expect(requestBid.imp[0].video.w).to.equal(640); + expect(requestBid.imp[0].video.h).to.equal(480); + expect(requestBid.imp[0].video.playerSize).to.be.undefined; + expect(requestBid.imp[0].video.context).to.be.undefined; + }); + } it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); @@ -994,7 +998,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1021,7 +1025,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1383,7 +1387,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1522,7 +1526,7 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.not.exist; expect(requestBid.app).to.exist.and.to.be.a('object'); @@ -1732,9 +1736,9 @@ describe('S2S Adapter', function () { const s2sBidRequest = utils.deepClone(REQUEST); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; s2sBidRequest.s2sConfig = cookieSyncConfig; - + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); - + let bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); return JSON.parse(server.requests[0].requestBody); @@ -1762,7 +1766,7 @@ describe('S2S Adapter', function () { } }); }); - + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { const userSync = { filterSettings: { @@ -1944,7 +1948,7 @@ describe('S2S Adapter', function () { device: device }); - adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -2809,28 +2813,30 @@ describe('S2S Adapter', function () { expect(response).to.have.property('dealId', 'test-dealid'); }); - it('should pass through default adserverTargeting if present in bidObject for video request', function () { - config.setConfig({ s2sConfig: CONFIG }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); - const targetingTestData = { - hb_cache_path: '/cache', - hb_cache_host: 'prebid-cache.testurl.com' - }; + if (FEATURES.VIDEO) { + it('should pass through default adserverTargeting if present in bidObject for video request', function () { + config.setConfig({ s2sConfig: CONFIG }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); + const targetingTestData = { + hb_cache_path: '/cache', + hb_cache_host: 'prebid-cache.testurl.com' + }; - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.targeting = targetingTestData - }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.targeting = targetingTestData + }); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); }); - }); + } it('should set the bidResponse currency to whats in the PBS response', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -2961,60 +2967,62 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 30); }); - it('handles OpenRTB video responses', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } - }); - config.setConfig({ s2sConfig }); + if (FEATURES.VIDEO) { + it('handles OpenRTB video responses', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } + }); + config.setConfig({ s2sConfig }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); - expect(response).to.have.property('mediaType', 'video'); - expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('requestId', '123'); - expect(response).to.have.property('cpm', 10); - }); - - it('handles response cache from ext.prebid.cache.vastXml', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); + expect(response).to.have.property('mediaType', 'video'); + expect(response).to.have.property('bidderCode', 'appnexus'); + expect(response).to.have.property('requestId', '123'); + expect(response).to.have.property('cpm', 10); }); - config.setConfig({ s2sConfig }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.cache = { - vastXml: { - cacheId: 'abcd1234', - url: 'https://prebid-cache.net/cache?uuid=abcd1234' + + it('handles response cache from ext.prebid.cache.vastXml', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' } - } - }); + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.cache = { + vastXml: { + cacheId: 'abcd1234', + url: 'https://prebid-cache.net/cache?uuid=abcd1234' + } + } + }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'abcd1234'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); - }); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'abcd1234'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); + }); + } it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { const s2sConfig = Object.assign({}, CONFIG, { @@ -3033,20 +3041,22 @@ describe('S2S Adapter', function () { item.bid[0].ext.prebid.targeting = targetingTestData }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' - }); + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); + } }); it('handles response cache from ext.prebid.targeting', function () { @@ -3065,18 +3075,20 @@ describe('S2S Adapter', function () { } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'a5ad3993'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'a5ad3993'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + } }); it('handles response cache from ext.prebid.targeting with wurl', function () { @@ -3097,15 +3109,18 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('pbsBidId', '654321'); + } }); it('add request property pbsBidId with ext.prebid.bidid value', function () { @@ -3117,16 +3132,18 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + expect(response).to.have.property('pbsBidId', '654321'); + } }); if (FEATURES.NATIVE) { @@ -3281,6 +3298,70 @@ describe('S2S Adapter', function () { }); }); }); + describe('when the response contains ext.prebid.fledge', () => { + let fledgeStub, request, bidderRequests; + + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); + }); + + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); + config.setConfig({CONFIG}); + request = deepClone(REQUEST); + request.ad_units.forEach(au => deepSetValue(au, 'ortb2Imp.ext.ae', 1)); + bidderRequests = deepClone(BID_REQUESTS); + bidderRequests.forEach(req => req.fledgeEnabled = true); + }); + + const AU = 'div-gpt-ad-1460505748561-0'; + const FLEDGE_RESP = { + ext: { + prebid: { + fledge: { + auctionconfigs: [ + { + impid: AU, + config: { + id: 1 + } + }, + { + impid: AU, + config: { + id: 2 + } + } + ] + } + } + } + } + + it('calls addComponentAuction alongside addBidResponse', function () { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); + expect(addBidResponse.called).to.be.true; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }); + + it('calls addComponentAuction when there is no bid in the response', () => { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); + expect(addBidResponse.called).to.be.false; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }) + }); }); describe('bid won events', function () { diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js index d7b7a527485..780cc8b8fdb 100644 --- a/test/spec/modules/pubwiseBidAdapter_spec.js +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/pubwiseBidAdapter.js'; -import {_checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import {_checkVideoPlacement, _checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import {_parseAdSlot} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import * as utils from 'src/utils.js'; @@ -486,6 +486,28 @@ const samplePBBidObjects = [ ]; describe('PubWiseAdapter', function () { + describe('Handles Params Properly', function () { + it('properly sets the default endpoint', function () { + const referenceEndpoint = 'https://bid.pubwise.io/prebid'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + // endpointBidRequest.forEach((bidRequest) => { + // bidRequest.params.endpoint_url = newEndpoint; + // }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(referenceEndpoint); + }); + + it('allows endpoint to be reset', function () { + const newEndpoint = 'http://www.pubwise.io/endpointtest'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + endpointBidRequest.forEach((bidRequest) => { + bidRequest.params.endpoint_url = newEndpoint; + }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(newEndpoint); + }); + }); + describe('Properly Validates Bids', function () { it('valid bid', function () { let validBid = { @@ -555,14 +577,14 @@ describe('PubWiseAdapter', function () { it('identifies native adm type', function() { let adm = '{"ver":"1.2","assets":[{"title":{"text":"PubWise Test"}},{"img":{"type":3,"url":"http://www.pubwise.io"}},{"img":{"type":1,"url":"http://www.pubwise.io"}},{"data":{"type":2,"value":"PubWise Test Desc"}},{"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":""}}'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('native', adm + ' Is a Native adm'); }); it('identifies banner adm type', function() { let adm = '<div style="box-sizing: border-box;width:298px;height:248px;border: 1px solid rgba(0,0,0,.25);border-radius:10px;">↵ <h3 style="margin-top:80px;text-align: center;">PubWise Test Bid</h3>↵</div>'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('banner', adm + ' Is a Banner adm'); }); }); @@ -582,4 +604,292 @@ describe('PubWiseAdapter', function () { expect(pbResponse).to.deep.equal(samplePBBidObjects); }); }); + + describe('Video Testing', function () { + /** + * Video Testing + */ + + const videoBidRequests = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '22bddb28db77d', + adUnitCode: 'Div1', + params: { + siteId: 'xxxxxx', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 10, + maxbitrate: 10 + } + } + } + ]; + + let newvideoRequests = [{ + 'bidder': 'pwbid', + 'params': { + 'siteId': 'xxxxx', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', + 'ext': { + 'buyid': 'myBuyId' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "<VAST version='3.0'><Ad id='601364'><InLine><AdSystem>Acudeo Compatible</AdSystem><AdTitle>VAST 2.0 Instream Test 1</AdTitle><Description>VAST 2.0 Instream Test 1</Description><Creatives><Creative AdID='601364'><Linear skipoffset='20%'><TrackingEvents><Tracking event='close'><![CDATA[https://pwtracking.com/linear/close]]></Tracking><Tracking event='skip'><![CDATA[https://pwtracking.com/linear/skip]]></Tracking><MediaFiles><MediaFile delivery='progressive' type='video/mp4' bitrate='500' width='400' height='300' scalable='true' maintainAspectRatio='true'><![CDATA[https://localhost/pubwise.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>", + 'w': 0, + 'h': 0 + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + }; + + let videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '<VAST version="3.0"><Ad id="601364"><!--not real sample--></Ad></VAST>', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = request.data; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('Div1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); + + expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); + expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); + + expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); + expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); + + expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); + expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); + + expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); + expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); + + expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); + expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); + expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); + + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video'); + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should process instream and outstream', function() { + let validOutstreamRequest = + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let outstreamBidRequest = + [ + validOutstreamRequest + ]; + + let validInstreamRequest = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let instreamBidRequest = + [ + validInstreamRequest + ]; + + let outstreamRequest = spec.isBidRequestValid(validOutstreamRequest); + expect(outstreamRequest).to.equal(false); + + let instreamRequest = spec.isBidRequestValid(validInstreamRequest); + expect(instreamRequest).to.equal(true); + }); + + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'DivCheckPlacement'; + const msg_placement_missing = 'PubWise: Video.Placement param missing for DivCheckPlacement'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], + } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + _checkVideoPlacement(videoData, adUnit); + // when failing this gives an odd message about "AssertError: expected logWarn to be called with arguments" it means the specific message expected + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + _checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) + }); + // end video testing + }); }); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index e51a4e2e3a2..221efc2d374 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -50,6 +50,7 @@ describe('RealVu', function() { describe('Analytics Adapter.', function () { it('enableAnalytics', function () { + this.timeout(3500) const config = { options: { partnerId: '1Y', diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 6d41df7605b..792d3ab0a9e 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -53,16 +53,18 @@ describe('RTBHouseAdapter', () => { describe('buildRequests', function () { let bidRequests; - const bidderRequest = { - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'] - } - }; + let bidderRequest; beforeEach(() => { + bidderRequest = { + 'auctionId': 'bidderrequest-auction-id', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; bidRequests = [ { 'bidder': 'rtbhouse', @@ -82,6 +84,11 @@ describe('RTBHouseAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'transactionId': 'example-transaction-id', + 'ortb2Imp': { + 'ext': { + 'tid': 'ortb2Imp-transaction-id-1' + } + }, 'schain': { 'ver': '1.0', 'complete': 1, @@ -203,7 +210,7 @@ describe('RTBHouseAdapter', () => { const bidRequest = Object.assign([], bidRequests); const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); - expect(data.source.tid).to.equal('example-transaction-id'); + expect(data.source.tid).to.equal('bidderrequest-auction-id'); }); it('should include bidfloor from floor module if avaiable', () => { @@ -256,6 +263,13 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.have.deep.property('tid'); }); + it('should include impression level transaction id when provided', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.tid).to.equal('ortb2Imp-transaction-id-1'); + }); + it('should not include invalid schain', () => { const bidRequest = Object.assign([], bidRequests); bidRequest[0].schain = { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 034536d4973..d535dc84411 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -319,7 +319,6 @@ describe('the rubicon adapter', function () { accountId: '14062', siteId: '70608', zoneId: '335918', - pchain: 'GAM:11111-reseller1:22222', userId: '12346', keywords: ['a', 'b', 'c'], inventory: { @@ -430,7 +429,6 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -598,20 +596,11 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); - it('should not send x_source.pchain to AE if params.pchain is not specified', function () { - var noPchainRequest = utils.deepClone(bidderRequest); - delete noPchainRequest.bids[0].params.pchain; - - let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); - expect(request.data).to.contain('&site_id=70608&'); - expect(request.data).to.not.contain('x_source.pchain'); - }); - it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -1549,709 +1538,711 @@ describe('the rubicon adapter', function () { }); }); - describe('for video requests', function () { - it('should make a well-formed video request', function () { - createVideoBidderRequest(); - - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - let post = request.data; - - expect(post).to.have.property('imp'); - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); // now undefined - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - expect(imp.video.minduration).to.equal(15); - expect(imp.video.maxduration).to.equal(30); - expect(imp.video.startdelay).to.equal(0); - expect(imp.video.skip).to.equal(1); - expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.prebid.bidder.rubicon.video.size_id).to.equal(201); - expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); - // Also want it to be in post.site.content.language - expect(imp.ext.prebid.bidder.rubicon.video.skip).to.equal(1); - expect(imp.ext.prebid.bidder.rubicon.video.skipafter).to.equal(15); - expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); - // should contain version - expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); - expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - // EIDs should exist - expect(post.user.ext).to.have.property('eids').that.is.an('array'); - // LiveIntent should exist - expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); - expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); - expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); - expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); - expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); - expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); - // LiveRamp should exist - expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); - expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); - expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); - // UnifiedId should exist - expect(post.user.ext.eids[2].source).to.equal('adserver.org'); - expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); - // PubCommonId should exist - expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); - expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); - // example should exist - expect(post.user.ext.eids[4].source).to.equal('example.com'); - expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); - // id-partner.com - expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); - expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); - // CriteoId should exist - expect(post.user.ext.eids[6].source).to.equal('criteo.com'); - expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }); - - describe('ortb2imp sent to video bids', function () { - beforeEach(function () { - // initialize - if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { - delete bidderRequest.bids[0].ortb2Imp; - } - }); - - it('should add ortb values to video requests', function () { + if (FEATURES.VIDEO) { + describe('for video requests', function () { + it('should make a well-formed video request', function () { createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - bidderRequest.bids[0].ortb2Imp = { - ext: { - gpid: '/test/gpid', - data: { - pbadslot: '/test/pbadslot' - }, - prebid: { - storedauctionresponse: { - id: 'sample_video_response' - } - } - } - } - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); let post = request.data; expect(post).to.have.property('imp'); // .with.length.of(1); let imp = post.imp[0]; - expect(imp.ext.gpid).to.equal('/test/gpid'); - expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); - expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); - }); - }); + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); // now undefined + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.size_id).to.equal(201); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language + expect(imp.ext.prebid.bidder.rubicon.video.skip).to.equal(1); + expect(imp.ext.prebid.bidder.rubicon.video.skipafter).to.equal(15); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + // should contain version + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + // EIDs should exist + expect(post.user.ext).to.have.property('eids').that.is.an('array'); + // LiveIntent should exist + expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); + expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); + expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); + expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); + expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); + expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); + // LiveRamp should exist + expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); + expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); + // UnifiedId should exist + expect(post.user.ext.eids[2].source).to.equal('adserver.org'); + expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); + // PubCommonId should exist + expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); + expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); + // example should exist + expect(post.user.ext.eids[4].source).to.equal('example.com'); + expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); + // id-partner.com + expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); + // CriteoId should exist + expect(post.user.ext.eids[6].source).to.equal('criteo.com'); + expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }); + + describe('ortb2imp sent to video bids', function () { + beforeEach(function () { + // initialize + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; + } + }); - it('should correctly set bidfloor on imp when getfloor in scope', function () { - createVideoBidderRequest(); - // default getFloor response is empty object so should not break and not send hard_floor - bidderRequest.bids[0].getFloor = () => getFloorResponse; - sinon.spy(bidderRequest.bids[0], 'getFloor'); + it('should add ortb values to video requests', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/test/gpid', + data: { + pbadslot: '/test/pbadslot' + }, + prebid: { + storedauctionresponse: { + id: 'sample_video_response' + } + } + } + } - // make sure banner bid called with right stuff - expect( - bidderRequest.bids[0].getFloor.calledWith({ - currency: 'USD', - mediaType: '*', - size: '*' - }) - ).to.be.true; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - // not an object should work and not send - expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(post).to.have.property('imp'); + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.ext.gpid).to.equal('/test/gpid'); + expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); + expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); + }); + }); - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR', floor: 1.0}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + it('should correctly set bidfloor on imp when getfloor in scope', function () { + createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => getFloorResponse; + sinon.spy(bidderRequest.bids[0], 'getFloor'); - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - // make it respond with USD floor and string floor - getFloorResponse = {currency: 'USD', floor: '1.23'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // make it respond with USD floor and num floor - getFloorResponse = {currency: 'USD', floor: 1.23}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); - }); + // make sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + }) + ).to.be.true; - it('should continue with auction if getFloor throws error', function () { - createVideoBidderRequest(); - // default getFloor response is empty object so should not break and not send hard_floor - bidderRequest.bids[0].getFloor = () => { - throw new Error('An exception!'); - }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + // not an object should work and not send + expect(request.data.imp[0].bidfloor).to.be.undefined; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR', floor: 1.0}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // should have an imp - expect(request.data.imp).to.exist.and.to.be.a('array'); - expect(request.data.imp).to.have.lengthOf(1); + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // should be NO bidFloor - expect(request.data.imp[0].bidfloor).to.be.undefined; - expect(request.data.imp[0].bidfloorcur).to.be.undefined; - }); + // make it respond with USD floor and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); - it('should add alias name to PBS Request', function () { - createVideoBidderRequest(); - adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; - bidderRequest.bidderCode = 'superRubicon'; - bidderRequest.bids[0].bidder = 'superRubicon'; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // make it respond with USD floor and num floor + getFloorResponse = {currency: 'USD', floor: 1.23}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); + }); - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); - expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); + it('should continue with auction if getFloor throws error', function () { + createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => { + throw new Error('An exception!'); + }; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - // should have the imp ext bidder params be under the alias name not rubicon superRubicon - expect(request.data.imp[0].ext.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); - expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); - }); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - it('should add floors flag correctly to PBS Request', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should have an imp + expect(request.data.imp).to.exist.and.to.be.a('array'); + expect(request.data.imp).to.have.lengthOf(1); - // should not pass if undefined - expect(request.data.ext.prebid.floors).to.be.undefined; + // should be NO bidFloor + expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; + }); - // should pass it as false - bidderRequest.bids[0].floorData = { - skipped: false, - location: 'fetch', - } - let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); - }); + it('should add alias name to PBS Request', function () { + createVideoBidderRequest(); + adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; + bidderRequest.bidderCode = 'superRubicon'; + bidderRequest.bids[0].bidder = 'superRubicon'; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - it('should add multibid configuration to PBS Request', function () { - createVideoBidderRequest(); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); + expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); - const multibid = [{ - bidder: 'bidderA', - maxBids: 2 - }, { - bidder: 'bidderB', - maxBids: 2 - }]; - const expected = [{ - bidder: 'bidderA', - maxbids: 2 - }, { - bidder: 'bidderB', - maxbids: 2 - }]; + // should have the imp ext bidder params be under the alias name not rubicon superRubicon + expect(request.data.imp[0].ext.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); + }); - config.setConfig({multibid: multibid}); + it('should add floors flag correctly to PBS Request', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should not pass if undefined + expect(request.data.ext.prebid.floors).to.be.undefined; - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); - expect(request.data.ext.prebid.multibid).to.deep.equal(expected); - }); + // should pass it as false + bidderRequest.bids[0].floorData = { + skipped: false, + location: 'fetch', + } + let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); + }); - it('should pass client analytics to PBS endpoint if all modules included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = []; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); - it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); - it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + it('should pass client analytics to PBS endpoint if all modules included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - expect(payload.ext.prebid.analytics).to.be.undefined; - }); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - it('should send video exp param correctly when set', function () { - createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - // should exp set to the right value according to config - let imp = post.imp[0]; - expect(imp.exp).to.equal(600); - }); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - it('should not send video exp at all if not set in s2sConfig config', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - // should exp set to the right value according to config - let imp = post.imp[0]; - // bidderFactory stringifies request body before sending so removes undefined attributes: - expect(imp.exp).to.equal(undefined); - }); + expect(payload.ext.prebid.analytics).to.be.undefined; + }); - it('should send tmax as the bidderRequest timeout value', function () { - createVideoBidderRequest(); - bidderRequest.timeout = 3333; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - expect(post.tmax).to.equal(3333); - }); + it('should send video exp param correctly when set', function () { + createVideoBidderRequest(); + config.setConfig({s2sConfig: {defaultTtl: 600}}); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - it('should send correct bidfloor to PBS', function () { - createVideoBidderRequest(); + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); - bidderRequest.bids[0].params.floor = 0.1; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0.1); + it('should not send video exp at all if not set in s2sConfig config', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - bidderRequest.bids[0].params.floor = 5.5; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(5.5); + // should exp set to the right value according to config + let imp = post.imp[0]; + // bidderFactory stringifies request body before sending so removes undefined attributes: + expect(imp.exp).to.equal(undefined); + }); - bidderRequest.bids[0].params.floor = '1.7'; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.7); + it('should send tmax as the bidderRequest timeout value', function () { + createVideoBidderRequest(); + bidderRequest.timeout = 3333; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + expect(post.tmax).to.equal(3333); + }); - bidderRequest.bids[0].params.floor = 0; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0); + it('should send correct bidfloor to PBS', function () { + createVideoBidderRequest(); - bidderRequest.bids[0].params.floor = undefined; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + bidderRequest.bids[0].params.floor = 0.1; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0.1); - bidderRequest.bids[0].params.floor = null; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - }); + bidderRequest.bids[0].params.floor = 5.5; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(5.5); - it('should send request with proper ad position', function () { - createVideoBidderRequest(); - let positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].mediaTypes.video.pos = 1; - let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = undefined; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'atf' - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'btf'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(3); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'foobar'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - }); + bidderRequest.bids[0].params.floor = '1.7'; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.7); - it('should properly enforce video.context to be either instream or outstream', function () { - let bid = bidderRequest.bids[0]; - bid.mediaTypes = { - video: { - context: 'instream', - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [2, 5], - maxduration: 30, - linearity: 1, - api: [2] + bidderRequest.bids[0].params.floor = 0; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0); + + bidderRequest.bids[0].params.floor = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + + bidderRequest.bids[0].params.floor = null; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + }); + + it('should send request with proper ad position', function () { + createVideoBidderRequest(); + let positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].mediaTypes.video.pos = 1; + let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = undefined; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf' + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(3); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'foobar'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + }); + + it('should properly enforce video.context to be either instream or outstream', function () { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } } - } - bid.params.video = {}; + bid.params.video = {}; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to outstream, still true - bidRequestCopy.mediaTypes.video.context = 'outstream'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + // change context to outstream, still true + bidRequestCopy.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to random, false now - bidRequestCopy.mediaTypes.video.context = 'random'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to random, false now + bidRequestCopy.mediaTypes.video.context = 'random'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // change context to undefined, still false - bidRequestCopy.mediaTypes.video.context = undefined; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to undefined, still false + bidRequestCopy.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // remove context, still false - delete bidRequestCopy.mediaTypes.video.context; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - }); + // remove context, still false + delete bidRequestCopy.mediaTypes.video.context; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); - it('should enforce the new required mediaTypes.video params', function () { - createVideoBidderRequest(); + it('should enforce the new required mediaTypes.video params', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - // change mimes to a non array, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change mimes to a non array, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete mimes, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.mimes; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete mimes, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change protocols to an int not array of ints, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.protocols = 1; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change protocols to an int not array of ints, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete protocols, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.protocols; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete protocols, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change linearity to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change linearity to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete linearity, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.linearity; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete linearity, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.linearity; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change api to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.api = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change api to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.api = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete api, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.api; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - }); + // delete api, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.api; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + }); - it('bid request is valid when video context is outstream', function () { - createVideoBidderRequestOutstream(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + it('bid request is valid when video context is outstream', function () { + createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); - }); + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); + }); - it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { + it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { // add banner and video mediaTypes - bidderRequest.mediaTypes = { - banner: { - sizes: [[300, 250]] - }, - video: { - context: 'outstream' - } - }; - // no video object in rubicon params, so we should see one call made for banner + bidderRequest.mediaTypes = { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + } + }; + // no video object in rubicon params, so we should see one call made for banner - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - bidderRequest.mediaTypes.video.context = 'instream'; + bidderRequest.mediaTypes.video.context = 'instream'; - requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); - it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { - createVideoBidderRequestNoVideo(); + it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { + createVideoBidderRequestNoVideo(); - let bid = bidderRequest.bids[0]; - bid.mediaTypes.banner = { - sizes: [[300, 250]] - }; + let bid = bidderRequest.bids[0]; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); - it('should include coppa flag in video bid request', () => { - createVideoBidderRequest(); + it('should include coppa flag in video bid request', () => { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.regs.coppa).to.equal(1); }); - const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.data.regs.coppa).to.equal(1); - }); - it('should include first party data', () => { - createVideoBidderRequest(); + it('should include first party data', () => { + createVideoBidderRequest(); - const site = { - ext: { - data: { - page: 'home' - } - }, - content: { + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] + }, + keywords: 'e,f', + rating: '4-star', data: [{foo: 'bar'}] - }, - keywords: 'e,f', - rating: '4-star', - data: [{foo: 'bar'}] - }; - const user = { - ext: { - data: { - age: 31 - } - }, - keywords: 'd', - gender: 'M', - yob: '1984', - geo: {country: 'ca'}, - data: [{foo: 'bar'}] - }; + }; + const user = { + ext: { + data: { + age: 31 + } + }, + keywords: 'd', + gender: 'M', + yob: '1984', + geo: {country: 'ca'}, + data: [{foo: 'bar'}] + }; - const ortb2 = { - site, - user - }; + const ortb2 = { + site, + user + }; - const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); - const expected = { - site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), - user: Object.assign({}, user), - siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), - userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), - }; + const expected = { + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), + }; - delete request.data.site.page; - delete request.data.site.content.language; + delete request.data.site.page; + delete request.data.site.content.language; - expect(request.data.site.keywords).to.deep.equal('a,b,c'); - expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.siteData); - expect(request.data.user.ext.data).to.deep.equal(expected.userData); - }); + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); + }); - it('should include pbadslot in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - pbadslot: '1234567890' + it('should include pbadslot in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); + }); - it('should NOT include storedrequests in pbs payload', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2 = { - ext: { - prebid: { - storedrequest: 'no-send-top-level-sr' + it('should NOT include storedrequests in pbs payload', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2 = { + ext: { + prebid: { + storedrequest: 'no-send-top-level-sr' + } } } - } - bidderRequest.bids[0].ortb2Imp = { - ext: { - prebid: { - storedrequest: 'no-send-imp-sr' + bidderRequest.bids[0].ortb2Imp = { + ext: { + prebid: { + storedrequest: 'no-send-imp-sr' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.storedrequest).to.be.undefined; - expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.storedrequest).to.be.undefined; + expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; + }); - it('should include GAM ad unit in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - adserver: { - adslot: '1234567890', - name: 'adServerName1' + it('should include GAM ad unit in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } - } - }; + }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); + }); - it('should use the integration type provided in the config instead of the default', () => { - createVideoBidderRequest(); - config.setConfig({rubicon: {int_type: 'testType'}}); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); - }); + it('should use the integration type provided in the config instead of the default', () => { + createVideoBidderRequest(); + config.setConfig({rubicon: {int_type: 'testType'}}); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); + }); - it('should pass the user.id provided in the config', function () { - config.setConfig({user: {id: '123'}}); - createVideoBidderRequest(); + it('should pass the user.id provided in the config', function () { + config.setConfig({user: {id: '123'}}); + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - let post = request.data; - - expect(post).to.have.property('imp') - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - expect(imp.video.minduration).to.equal(15); - expect(imp.video.maxduration).to.equal(30); - expect(imp.video.startdelay).to.equal(0); - expect(imp.video.skip).to.equal(1); - expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); - - // Also want it to be in post.site.content.language - expect(post.site.content.language).to.equal('en'); - expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); - expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - - // Config user.id - expect(post.user.id).to.equal('123'); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }) - }); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let post = request.data; + + expect(post).to.have.property('imp') + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + + // Also want it to be in post.site.content.language + expect(post.site.content.language).to.equal('en'); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + + // Config user.id + expect(post.user.id).to.equal('123'); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }) + }); + } describe('combineSlotUrlParams', function () { it('should combine an array of slot url params', function () { @@ -3219,64 +3210,66 @@ describe('the rubicon adapter', function () { }); }); - describe('for video', function () { - beforeEach(function () { - createVideoBidderRequest(); - }); + if (FEATURES.VIDEO) { + describe('for video', function () { + beforeEach(function () { + createVideoBidderRequest(); + }); - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } }, - type: 'video' + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - } + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; + }; - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - let bids = spec.interpretResponse({body: response}, {data: request}); + let bids = spec.interpretResponse({body: response}, {data: request}); - expect(bids).to.be.lengthOf(1); + expect(bids).to.be.lengthOf(1); - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(480); + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + }); }); - }); + } if (FEATURES.NATIVE) { describe('for native', () => { @@ -3290,152 +3283,154 @@ describe('the rubicon adapter', function () { }); } - describe('for outstream video', function () { - const sandbox = sinon.createSandbox(); - beforeEach(function () { - createVideoBidderRequestOutstream(); - config.setConfig({rubicon: { - rendererConfig: { - align: 'left', - closeButton: true - }, - rendererUrl: 'https://example.test/renderer.js' - }}); - window.MagniteApex = { - renderAd: function() { - return null; + if (FEATURES.VIDEO) { + describe('for outstream video', function () { + const sandbox = sinon.createSandbox(); + beforeEach(function () { + createVideoBidderRequestOutstream(); + config.setConfig({rubicon: { + rendererConfig: { + align: 'left', + closeButton: true + }, + rendererUrl: 'https://example.test/renderer.js' + }}); + window.MagniteApex = { + renderAd: function() { + return null; + } } - } - }); + }); - afterEach(function () { - sandbox.restore(); - delete window.MagniteApex; - }); + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } }, - type: 'video' + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - } + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; - - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - let bids = spec.interpretResponse({body: response}, { data: request }); - - expect(bids).to.be.lengthOf(1); - - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(320); - // check custom renderer - expect(typeof bids[0].renderer).to.equal('object'); - expect(bids[0].renderer.getConfig()).to.deep.equal({ - align: 'left', - closeButton: true - }); - expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); - }); + }; - it('should render ad with Magnite renderer', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, { data: request }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(320); + // check custom renderer + expect(typeof bids[0].renderer).to.equal('object'); + expect(bids[0].renderer.getConfig()).to.deep.equal({ + align: 'left', + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' } }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' - }, - type: 'video' - } - }, - nurl: 'https://test.com/vast.xml' + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; - - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - sinon.spy(window.MagniteApex, 'renderAd'); + }; - let bids = spec.interpretResponse({body: response}, {data: request}); - const bid = bids[0]; - bid.adUnitCode = 'outstream_video1_placement'; - const adUnit = document.createElement('div'); - adUnit.id = bid.adUnitCode; - document.body.appendChild(adUnit); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - bid.renderer.render(bid); + sinon.spy(window.MagniteApex, 'renderAd'); - const renderCall = window.MagniteApex.renderAd.getCall(0); - expect(renderCall.args[0]).to.deep.equal({ - closeButton: true, - collapse: true, - height: 320, - label: undefined, - placement: { - align: 'left', - attachTo: adUnit, - position: 'append', - }, - vastUrl: 'https://test.com/vast.xml', - width: 640 + let bids = spec.interpretResponse({body: response}, {data: request}); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 320, + label: undefined, + placement: { + align: 'left', + attachTo: adUnit, + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); }); - // cleanup - adUnit.parentNode.removeChild(adUnit); }); - }); + } describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index 1aa7b132221..30e95b04ccf 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -5,7 +5,7 @@ import {VIDEO, BANNER} from 'src/mediaTypes.js' const bidderRequest = { refererInfo: { - referer: 'https://example.com' + canonicalUrl: 'https://example.com' } } @@ -19,6 +19,8 @@ const gdpr = { } } +const uspConsent = '1---'; + const schain = { 'schain': { 'validation': 'strict', @@ -338,18 +340,28 @@ describe('shBidAdapter', function () { }); }) - it('passes gdpr if present', function () { - const request = spec.buildRequests([bidRequestVideo], {...bidderRequest, ...gdpr}) + it('passes gdpr & uspConsent if present', function () { + const request = spec.buildRequests([bidRequestVideo], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const payload = request.data.requests[0]; expect(payload).to.be.an('object'); expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) + expect(payload.uspConsent).to.eql(uspConsent) }) - it('passes gdpr if present (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], {...bidderRequest, ...gdpr}) + it('passes gdpr & usp if present (V2)', function () { + const request = spec.buildRequests([bidRequestVideoV2], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const context = request.data.context; expect(context).to.be.an('object'); expect(context.gdprConsent).to.eql(gdpr.gdprConsent) + expect(context.uspConsent).to.eql(uspConsent) }) it('passes schain object if present', function() { @@ -379,7 +391,7 @@ describe('shBidAdapter', function () { expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) }) - const vastTag = 'https://video-library.stage.showheroes.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' + const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' const vastXml = '<?xml version="1.0" encoding="utf-8"?><VAST version="3.0"><Error><![CDATA[https://static.showheroes.com/shim.gif]]></Error></VAST>' const basicResponse = { @@ -389,7 +401,7 @@ describe('shBidAdapter', function () { 'context': 'instream', 'bidId': '38b373e1e31c18', 'size': {'width': 640, 'height': 480}, - 'vastTag': 'https:\/\/video-library.stage.showheroes.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', + 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', 'vastXml': vastXml, 'adomain': adomain, }; @@ -423,13 +435,13 @@ describe('shBidAdapter', function () { 'height': 480, 'advertiserDomain': [], 'callbacks': { - 'won': ['https://api-n729.qa.viralize.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] + 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] }, 'mediaType': 'video', 'adomain': adomain, }; - const vastUrl = 'https://api-n729.qa.viralize.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' + const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' const responseVideoV2 = { 'bidResponses': [{ @@ -443,7 +455,7 @@ describe('shBidAdapter', function () { 'bidResponses': [{ ...basicResponseV2, 'context': 'outstream', - 'ad': '<script id="testScript" data-wid="auto" type="text/javascript" src="https://ads.viralize.tv/display/?zid=AACBTwsZVANd9NlB&u=https%3A%2F%2Fexample.org%2F%3Ffoo%3Dbar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ececb3b4c19270d6a77ccf75433001&width=300&height=200&prebidmode=1"></script>', + 'ad': '<script id="testScript" data-wid="auto" type="text/javascript" src="https://test.tv/display/?zid=AACBTwsZVANd9NlB&u=https%3A%2F%2Fexample.org%2F%3Ffoo%3Dbar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ececb3b4c19270d6a77ccf75433001&width=300&height=200&prebidmode=1"></script>', }], }; @@ -533,10 +545,6 @@ describe('shBidAdapter', function () { expect(renderer.config.vastUrl).to.equal(vastTag) renderer.render(bid) - // TODO: fix these. our tests should not be reliant on third-party scripts. wtf - // const scripts = document.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) - const spots = document.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -590,8 +598,6 @@ describe('shBidAdapter', function () { renderer.render(bid) const iframeDocument = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document) - // const scripts = iframeDocument.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = iframeDocument.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -602,8 +608,6 @@ describe('shBidAdapter', function () { customRender: function (bid, embedCode) { const container = document.createElement('div') container.appendChild(embedCode) - // const scripts = container.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = container.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 27fd7a33c14..16c1527a3ad 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -445,6 +445,10 @@ describe('sizeMappingV2', function () { }); describe('video mediaTypes checks', function () { + if (!FEATURES.VIDEO) { + return; + } + beforeEach(function () { sinon.spy(adUnitSetupChecks, 'validateVideoMediaType'); }); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 7891a49b9db..7f727d5d9e3 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -356,6 +356,13 @@ describe('smaatoBidAdapterTest', () => { keywords: 'a,b', gender: 'M', yob: 1984 + }, + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } } }; @@ -368,6 +375,9 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); expect(req.site.keywords).to.eql('power tools,drills'); expect(req.site.publisher.id).to.equal('publisherId'); + expect(req.device.ifa).to.equal('ifa'); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); }); it('has no user ids', () => { @@ -1007,6 +1017,28 @@ describe('smaatoBidAdapterTest', () => { expect(req.device.ifa).to.equal(DEVICE_ID); }); + it('when geo and ifa info present and fpd present, then prefer fpd', () => { + const ortb2 = { + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } + } + }; + + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {ifa: DEVICE_ID, geo: LOCATION}; + + const reqs = spec.buildRequests([inAppBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); + expect(req.device.ifa).to.equal('ifa'); + }); + it('when ifa is present but geo is missing, then add only ifa to device object', () => { const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); inAppBidRequest.params.app = {ifa: DEVICE_ID}; diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index b41b0280235..6b3147859bf 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -138,10 +138,13 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(mediaType, size) { +function mockBidRequestListData(mediaType, size, customSizes) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); let mediaTypes; + let params = { + endpointId: id + } if (mediaType == 'video') { mediaTypes = { @@ -158,13 +161,15 @@ function mockBidRequestListData(mediaType, size) { } } + if (customSizes === undefined || customSizes.length > 0) { + params.sizes = customSizes + } + return { adUnitCode: `adUnitCode-${id}`, mediaTypes: mediaTypes, bidId: `bidId-${id}`, - params: { - endpointId: id - } + params: params } }); } @@ -211,7 +216,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData('banner', 8); + mockBidRequest = mockBidRequestListData('banner', 8, []); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -238,13 +243,45 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { }); }); +describe('SmartyTechDSPAdapter: buildRequests banner custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('banner', 8, [[300, 600]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + +describe('SmartyTechDSPAdapter: buildRequests video custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('video', 8, [[300, 300], [250, 250]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.video.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + describe('SmartyTechDSPAdapter: interpretResponse', () => { let mockBidRequest; let mockReferer; let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('banner', 2); + const brData = mockBidRequestListData('banner', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -290,7 +327,7 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('video', 2); + const brData = mockBidRequestListData('video', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 56ed4d5196e..b9bd0dc4d9f 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -238,6 +238,13 @@ describe('SonobiBidAdapter', function () { }); describe('.buildRequests', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + sonobi: { + storageAllowed: true + } + }; + }); let sandbox; beforeEach(function () { sinon.stub(userSync, 'canBidderRegisterSync'); @@ -389,6 +396,10 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.coppa).to.equal(0); }); + it('should have storageAllowed set to true', function () { + expect($$PREBID_GLOBAL$$.bidderSettings.sonobi.storageAllowed).to.be.true; + }); + it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) const bidRequestsPageViewID = spec.buildRequests(bidRequest, bidderRequests) @@ -398,6 +409,8 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.ref).not.to.be.empty expect(bidRequests.data.s).not.to.be.empty expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(JSON.parse(bidRequests.data.iqid).pcid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) + expect(JSON.parse(bidRequests.data.iqid).pcidDate).to.match(/^[0-9]{13}$/) expect(bidRequests.data.hfa).to.not.exist expect(bidRequests.bidderRequests).to.eql(bidRequest); expect(bidRequests.data.ref).to.equal('overrides_top_window_location'); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js new file mode 100644 index 00000000000..d095fd3cf55 --- /dev/null +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -0,0 +1,402 @@ +import { expect } from 'chai'; +import { spec } from 'modules/stvBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; +const ENDPOINT_URL_DEV = 'https://ads.smartstream.tv/r/'; + +describe('stvAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + // banner + { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e2', + 'bidderRequestId': '22edbae2733bf62', + 'auctionId': '1d1a030790a476' + }, { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e3', + 'bidderRequestId': '22edbae2733bf69', + 'auctionId': '1d1a030790a477', + 'adUnitCode': 'testDiv2' + }, + // video + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + }, + }, + + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + } + + ]; + + // With gdprConsent + var bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: { someData: 'value' }, + gdprApplies: true + } + }; + + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + it('sends bid request 1 to our endpoint via GET', function() { + expect(request1.method).to.equal('GET'); + expect(request1.url).to.equal(ENDPOINT_URL); + let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + }); + + var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; + it('sends bid request 2 endpoint via GET', function() { + expect(request2.method).to.equal('GET'); + expect(request2.url).to.equal(ENDPOINT_URL); + let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&prebidDevMode=1&media_types%5Bbanner%5D=300x250'); + }); + + // Without gdprConsent + var bidderRequestWithoutGdpr = { + refererInfo: { + referer: 'some_referrer.net' + } + }; + var request3 = spec.buildRequests([bidRequests[2]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 3 without gdprConsent to our endpoint via GET', function() { + expect(request3.method).to.equal('GET'); + expect(request3.url).to.equal(ENDPOINT_URL); + let data = request3.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv2&media_types%5Bbanner%5D=300x250'); + }); + + var request4 = spec.buildRequests([bidRequests[3]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 4 (video) without gdprConsent endpoint via GET', function() { + expect(request4.method).to.equal('GET'); + expect(request4.url).to.equal(ENDPOINT_URL); + let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv3&media_types%5Bvideo%5D=640x480'); + }); + + var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 5 (video) to our endpoint via GET', function() { + expect(request5.method).to.equal('GET'); + expect(request5.url).to.equal(ENDPOINT_URL); + let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=40&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + + var request6 = spec.buildRequests([bidRequests[5]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 6 (video) to our endpoint via GET', function() { + expect(request6.method).to.equal('GET'); + expect(request6.url).to.equal(ENDPOINT_URL); + let data = request6.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'type': 'sspHTML', + 'tag': '<!-- test creative -->', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682', + 'adomain': ['bdomain'] + } + }; + let serverVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: ['bdomain'] }, + ad: '<!-- test creative -->', + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video', + }]; + + it('should get the correct bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains.length).to.equal(1); + expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); + }); + + it('should get the correct smartstream video bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + }); + + it('handles empty bid response', function() { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe(`getUserSyncs test usage`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '<!-- test creative -->', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function() { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, { consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function() { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function() { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); + + describe(`getUserSyncs test usage in passback response`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + reason: 8002, + status: 'error', + msg: 'passback', + } + }]; + }); + + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(0); + }); + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ pixelEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ pixelEnabled: true }, serverResponses).length).to.be.equal(0); + }); + }); +}); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 0c01244033e..206a0142043 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -668,6 +668,107 @@ describe('Taboola Adapter', function () { const res = spec.interpretResponse(serverResponse, request); expect(res).to.deep.equal(expectedRes); }); + + it('should replace AUCTION_PRICE macro in adm', function () { + const multiRequest = { + bids: [ + { + ...createBidRequest(), + ...displayBidRequestParams + }, + { + ...createBidRequest(), + ...displayBidRequestParams + } + ] + } + const multiServerResponseWithMacro = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '2', + 'price': 0.34, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + }, + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.35, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + const [bid] = multiServerResponseWithMacro.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: multiRequest.bids[1].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[0].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.34\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + }, + { + requestId: multiRequest.bids[0].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[1].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.35\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ]; + const res = spec.interpretResponse(multiServerResponseWithMacro, multiRequest); + expect(res).to.deep.equal(expectedRes); + }); }) describe('getUserSyncs', function () { diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 70d09513f27..4201ecc2329 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -1,29 +1,38 @@ -import { expect } from 'chai'; -import { spec, resetUserSync } from 'modules/underdogmediaBidAdapter.js'; +import { + expect +} from 'chai'; +import { + spec, + resetUserSync +} from 'modules/underdogmediaBidAdapter.js'; describe('UnderdogMedia adapter', function () { let bidRequests; let bidderRequest; beforeEach(function () { - bidRequests = [ - { - bidder: 'underdogmedia', - params: { - siteId: 12143 - }, - adUnitCode: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], - } - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; + bidRequests = [{ + bidder: 'underdogmedia', + params: { + siteId: 12143 + }, + adUnitCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [160, 600], + [320, 50] + ], + } + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }]; bidderRequest = { timeout: 3000, @@ -49,7 +58,10 @@ describe('UnderdogMedia adapter', function () { }, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -76,7 +88,10 @@ describe('UnderdogMedia adapter', function () { params: {}, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -86,90 +101,94 @@ describe('UnderdogMedia adapter', function () { }); it('request data should contain sid', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - bidder: 'underdogmedia', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sid).to.equal('12143'); + expect(request.data.sid).to.equal(12143); }); it('request data should contain sizes', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); }); it('request data should contain gdpr info', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.gdprApplies).to.equal(true); - expect(request.data.consentGiven).to.equal(true); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.gdpr.gdprApplies).to.equal(true); + expect(request.data.gdpr.consentGiven).to.equal(true); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should not build a request if no vendorConsent', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -189,22 +208,23 @@ describe('UnderdogMedia adapter', function () { }); it('should properly build a request if no vendorConsent but no gdprApplies', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -220,30 +240,32 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); - expect(request.data.gdprApplies).to.equal(false); - expect(request.data.consentGiven).to.equal(false); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); + expect(request.data.gdpr.gdprApplies).to.equal(false); + expect(request.data.gdpr.consentGiven).to.equal(false); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should properly build a request if gdprConsent empty', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -251,21 +273,663 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); }); it('should have uspConsent if defined', function () { const uspConsent = '1YYN' bidderRequest.uspConsent = uspConsent const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.equal(uspConsent); + expect(request.data.usp.uspConsent).to.equal(uspConsent); + }); + + it('should have correct number of placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements.length).to.equal(3); + }); + + it('should have correct adUnitCode for each placement', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements[0].adUnitCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(request.data.placements[1].adUnitCode).to.equal('div-gpt-ad-2460505748561-0'); + expect(request.data.placements[2].adUnitCode).to.equal('div-gpt-ad-3460505748561-0'); + }); + + it('should have gpid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal('/19968336/header-bid-tag-0'); + }); + + it('gpid should be undefined if it does not exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal(undefined); + }); + + it('should have productId equal to 1 if the productId is standard', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'standard' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have productId equal to 2 if the productId is adhesion', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'adhesion' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(2); + }); + + it('productId should default to 1 if it is not defined', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have correct sizes for multiple placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].sizes.length).to.equal(2); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('160x600'); + expect(request.data.placements[1].sizes.length).to.equal(1); + expect(request.data.placements[1].sizes[0]).to.equal('300x250'); + expect(request.data.placements[2].sizes.length).to.equal(1); + expect(request.data.placements[2].sizes[0]).to.equal('160x600'); + }); + + it('should have ref if it exists', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + }, + refererInfo: { + ref: 'www.example.com' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal('www.example.com'); + }); + + it('ref should be undefined if it does not exist', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal(undefined); + }); + + it('should have pubcid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal('ba6cbf43-abc0-4d61-b14f-e10f605b74d7'); + }); + + it('pubcid should be undefined if it does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal(undefined); + }); + + it('should have unifiedId if tdid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal('7a9fc5a2-346d-4502-826e-017a9badf5f3'); + }); + + it('unifiedId should be undefined if tdid does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal(undefined); }); - it('should not have uspConsent if not defined', function () { - bidderRequest.uspConsent = undefined + it('should have correct viewability information', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.be.undefined; + + expect(request.data.placements[0].viewability).to.equal(-1) }); }); @@ -273,26 +937,27 @@ describe('UnderdogMedia adapter', function () { it('should return complete bid response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - advertiser_domains: ['domain1'], - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - }, - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '250', - mid: '32633', - notification_url: 'notification_url', - tid: '2', - width: '300' - }, + mids: [{ + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + advertiser_domains: ['domain1'], + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160', + }, + { + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 3.0, + height: '250', + mid: '32633', + notification_url: 'notification_url', + tid: '2', + width: '300' + }, ] } }; @@ -326,17 +991,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on incorrect size', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '123', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '123', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -348,17 +1011,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on 0 cpm', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 0, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 0, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -370,17 +1031,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response if no ad in response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: '', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: '', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -392,17 +1051,16 @@ describe('UnderdogMedia adapter', function () { it('ad html string should contain the notification urls', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_cod_html', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_cod_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -430,15 +1088,14 @@ describe('UnderdogMedia adapter', function () { const responseWithUserSyncs = [{ body: { - userSyncs: [ - { - type: 'image', - url: 'https://test.url.com' - }, - { - type: 'iframe', - url: 'https://test.url.com' - } + userSyncs: [{ + type: 'image', + url: 'https://test.url.com' + }, + { + type: 'iframe', + url: 'https://test.url.com' + } ] } }]; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index bf27ef0ff81..33e2e2ea264 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -27,7 +27,7 @@ import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {dmdIdSubmodule} from 'modules/dmdIdSystem.js'; -import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; +import {liveIntentIdSubmodule, setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from 'modules/liveIntentIdSystem.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; @@ -147,6 +147,7 @@ describe('User ID', function () { hook.ready(); uninstallGdprEnforcement(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); + liveIntentIdSubmoduleDoNotFireEvent(); }); beforeEach(function () { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 24d565805d3..97f8af97339 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -110,6 +110,24 @@ const BIDDER_REQUEST = { 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } } }, }; @@ -281,6 +299,22 @@ describe('VidazooBidAdapter', function () { schain: VIDEO_BID.schain, sessionId: '', sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, @@ -330,6 +364,22 @@ describe('VidazooBidAdapter', function () { transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index b8a66e7c3b9..9a486cd6c34 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -1273,7 +1273,7 @@ describe('VisxAdapter', function () { it('onTimeout', function () { const data = [{ timeout: 3000, adUnitCode: 'adunit-code-1', auctionId: '1cbd2feafe5e8b', bidder: 'visx', bidId: '23423', params: [{ uid: '1' }] }]; - const expectedData = [{ ...data[0], params: [{ uid: 1 }] }]; + const expectedData = [{ timeout: 3000, params: [{ uid: 1 }] }]; spec.onTimeout(data); expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout//' + JSON.stringify(expectedData))).to.equal(true); }); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index df91100b966..12d413a9c93 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,34 +1,10 @@ import { assert, expect } from 'chai'; -import { spec } from 'modules/yandexBidAdapter.js'; +import { spec, NATIVE_ASSETS } from 'modules/yandexBidAdapter.js'; import { parseUrl } from 'src/utils.js'; -import { BANNER } from '../../../src/mediaTypes'; +import { BANNER, NATIVE } from '../../../src/mediaTypes'; +import {OPENRTB} from '../../../modules/rtbhouseBidAdapter'; describe('Yandex adapter', function () { - function getBidConfig() { - return { - bidder: 'yandex', - params: { - placementId: '123-1', - }, - }; - } - - function getBidRequest() { - return { - ...getBidConfig(), - bidId: 'bidid-1', - adUnitCode: 'adUnit-123', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600] - ], - }, - }, - }; - } - describe('isBidRequestValid', function () { it('should return true when required params found', function () { const bid = getBidRequest(); @@ -65,19 +41,17 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { - const gdprConsent = { - gdprApplies: 1, - consentString: 'concent-string', - apiVersion: 1, - }; - const bidderRequest = { refererInfo: { domain: 'ya.ru', ref: 'https://ya.ru/', page: 'https://ya.ru/', }, - gdprConsent + gdprConsent: { + gdprApplies: 1, + consentString: 'concent-string', + apiVersion: 1, + }, }; it('creates a valid banner request', function () { @@ -101,7 +75,7 @@ describe('Yandex adapter', function () { const { search: query } = parsedRequestUrl expect(parsedRequestUrl.hostname).to.equal('bs.yandex.ru'); - expect(parsedRequestUrl.pathname).to.equal('/metadsp/123'); + expect(parsedRequestUrl.pathname).to.equal('/prebid/123'); expect(query['imp-id']).to.equal('1'); expect(query['target-ref']).to.equal('ya.ru'); @@ -112,21 +86,197 @@ describe('Yandex adapter', function () { expect(request.data).to.exist; expect(data.site).to.not.equal(null); - expect(data.site.page_url).to.equal('https://ya.ru/'); - expect(data.site.ref_url).to.equal('https://ya.ru/'); + expect(data.site.page).to.equal('https://ya.ru/'); + expect(data.site.ref).to.equal('https://ya.ru/'); + }); + + describe('banner', () => { + it('should create valid banner object', () => { + const bannerRequest = getBidRequest({ + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + }, + } + }); - // expect(data.device).to.not.equal(null); - // expect(data.device.w).to.equal(window.innerWidth); - // expect(data.device.h).to.equal(window.innerHeight); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests[0].data.imp).to.have.lengthOf(1); - expect(data.imp).to.have.lengthOf(1); - expect(data.imp[0].banner).to.not.equal(null); - expect(data.imp[0].banner.w).to.equal(300); - expect(data.imp[0].banner.h).to.equal(250); + const imp = requests[0].data.imp[0]; + expect(imp.banner).to.not.equal(null); + expect(imp.banner.w).to.equal(300); + expect(imp.banner.h).to.equal(250); + + expect(imp.banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ]); + }); }); + + describe('native', () => { + function buildRequestAndGetNativeParams(extra) { + const bannerRequest = getBidRequest(extra); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + + return JSON.parse(requests[0].data.imp[0].native.request); + } + + it('should extract native params', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + title: { + required: true, + len: 100, + }, + body: { + len: 90 + }, + body2: { + len: 90 + }, + sponsoredBy: { + len: 25, + }, + icon: { + sizes: [32, 32], + }, + image: { + required: true, + sizes: [300, 250], + }, + }, + }, + }); + const sortedAssetsList = nativeParams.assets.sort((a, b) => a.id - b.id); + + expect(sortedAssetsList).to.deep.equal([ + { + id: NATIVE_ASSETS.title[0], + required: 1, + title: { + len: 100, + } + }, + { + id: NATIVE_ASSETS.body[0], + data: { + type: NATIVE_ASSETS.body[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.body2[0], + data: { + type: NATIVE_ASSETS.body2[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.sponsoredBy[0], + data: { + type: NATIVE_ASSETS.sponsoredBy[1], + len: 25, + }, + }, + { + id: NATIVE_ASSETS.icon[0], + img: { + type: NATIVE_ASSETS.icon[1], + w: 32, + h: 32, + }, + }, + { + id: NATIVE_ASSETS.image[0], + required: 1, + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }, + ]); + }); + + it('should parse multiple image sizes', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + sizes: [[300, 250], [100, 100]], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }); + }); + + it('should parse aspect ratios with min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + min_width: 320, + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 320, + hmin: 240, + }, + }); + }); + + it('should parse aspect ratios without min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 100, + hmin: 75, + }, + }); + }); + }) }); - describe('response handler', function () { + describe('interpretResponse', function () { const bannerRequest = getBidRequest(); const bannerResponse = { @@ -172,5 +322,125 @@ describe('Yandex adapter', function () { expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']); }); + + describe('native', () => { + function getNativeAdmResponse() { + return { + native: { + link: { + url: 'https://example.com' + }, + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [ + { + title: { + text: 'title text', + }, + id: NATIVE_ASSETS.title[0], + }, + { + data: { + value: 'body text' + }, + id: NATIVE_ASSETS.body[0], + }, + { + data: { + value: 'sponsoredBy text' + }, + id: NATIVE_ASSETS.sponsoredBy[0], + }, + { + img: { + url: 'https://example.com/image', + w: 200, + h: 150, + }, + id: NATIVE_ASSETS.image[0], + }, + { + img: { + url: 'https://example.com/icon', + h: 32, + w: 32 + }, + id: NATIVE_ASSETS.icon[0], + }, + ] + } + }; + } + + it('handles native responses', function() { + bannerRequest.bidRequest = { + mediaType: NATIVE, + bidId: 'bidid-1', + }; + + const nativeAdmResponce = getNativeAdmResponse(); + const bannerResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: 1, + price: 0.3, + adomain: [ + 'example.com' + ], + adid: 'yabs.123=', + adm: JSON.stringify(nativeAdmResponce), + }, + ], + }], + }, + }; + + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.exist; + + const bid = result[0]; + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + expect(bid.native).to.deep.equal({ + clickUrl: 'https://example.com', + impressionTrackers: ['https://example.com/imptracker'], + title: 'title text', + body: 'body text', + sponsoredBy: 'sponsoredBy text', + image: { + url: 'https://example.com/image', + width: 200, + height: 150, + }, + icon: { + url: 'https://example.com/icon', + width: 32, + height: 32, + }, + }); + }); + }); }); }); + +function getBidConfig() { + return { + bidder: 'yandex', + params: { + placementId: '123-1', + }, + }; +} + +function getBidRequest(extra = {}) { + return { + ...getBidConfig(), + bidId: 'bidid-1', + adUnitCode: 'adUnit-123', + ...extra, + }; +} diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 3cec0d79fcb..4e18f49c849 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,22 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const schain = { + complete: 1, + nodes: [ + { + asi: 'asi1', + sid: 'sid1', + rid: 'rid1' + }, + { + asi: 'asi2', + sid: 'sid2', + rid: 'rid2' + } + ] + }; + const params = { user: { uid: 222, @@ -103,6 +119,7 @@ describe('Zeta Ssp Bid Adapter', function () { gdprApplies: 1, consentString: 'consentString' }, + schain: schain, uspConsent: 'someCCPAString', params: params, userIdAsEids: eids, @@ -372,4 +389,11 @@ describe('Zeta Ssp Bid Adapter', function () { expect(spec.onTimeout).to.exist.and.to.be.a('function'); expect(spec.onTimeout({ timeout: 1000 })).to.be.undefined; }); + + it('Test schain provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.source.ext.schain).to.eql(schain); + }); }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2b7c2b88449..9150329ff60 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -14,6 +14,7 @@ import { import CONSTANTS from 'src/constants.json'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; +import {auctionManager} from '../../src/auctionManager.js'; const utils = require('src/utils'); const bid = { @@ -430,158 +431,180 @@ describe('native.js', function () { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); - it('creates native asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'assetRequest', - adId: '123', - assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], - }; + describe('native postMessages', () => { + let adUnit; + beforeEach(() => { + adUnit = {}; + sinon.stub(auctionManager, 'index').get(() => ({ + getAdUnit: () => adUnit + })) + }); + + it('creates native asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], + }; - const message = getAssetMessage(messageRequest, bid); + const message = getAssetMessage(messageRequest, bid); - expect(message.assets.length).to.equal(3); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, + expect(message.assets.length).to.equal(3); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); }); - }); - it('creates native all asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bid); + const message = getAllAssetsMessage(messageRequest, bid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); - it('creates native all asset message with only defined fields', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with only defined fields', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); - expect(message.assets.length).to.equal(4); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, + expect(message.assets.length).to.equal(4); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); }); - }); - it('creates native all asset message with complete format', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with complete format', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, completeNativeBid); + const message = getAllAssetsMessage(messageRequest, completeNativeBid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'privacyLink', - value: ortbBid.native.ortb.privacy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'privacyLink', + value: ortbBid.native.ortb.privacy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); + + it('if necessary, adds ortb response when the request was in ortb', () => { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + adUnit = {mediaTypes: {native: {ortb: ortbRequest}}, nativeOrtbRequest: ortbRequest} + const message = getAllAssetsMessage(messageRequest, bid); + const expected = toOrtbNativeResponse(bid.native, ortbRequest) + expect(message.ortb).to.eql(expected); + }) + }) const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ title: 'vtitle', @@ -1303,5 +1326,24 @@ describe('toOrtbNativeResponse', () => { text: 'vtitle' } }) + }); + + it('should accept objects as legacy assets', () => { + const legacyResponse = { + icon: { + url: 'image-url' + } + } + const request = toOrtbNativeRequest({ + icon: { + required: true + } + }); + const response = toOrtbNativeResponse(legacyResponse, request); + sinon.assert.match(response.assets[0], { + img: { + url: 'image-url' + } + }) }) }) diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 06366e8cc5c..58334595d71 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1602,12 +1602,15 @@ describe('adapterManager tests', function () { }); it('should add alias to registry when original adapter is using bidderFactory', function() { - let thisSpec = Object.assign(spec, { supportedMediaTypes: ['video'] }); + const mediaType = FEATURES.VIDEO ? 'video' : 'banner' + let thisSpec = Object.assign(spec, { supportedMediaTypes: [mediaType] }); registerBidder(thisSpec); const alias = 'aliasBidder'; adapterManager.aliasBidAdapter(CODE, alias); expect(adapterManager.bidderRegistry).to.have.property(alias); - expect(adapterManager.videoAdapters).to.include(alias); + if (FEATURES.VIDEO) { + expect(adapterManager.videoAdapters).to.include(alias); + } }); }); @@ -1963,64 +1966,6 @@ describe('adapterManager tests', function () { }); }); - describe('fledgeEnabled', function () { - const origRunAdAuction = navigator?.runAdAuction; - before(function () { - // navigator.runAdAuction doesn't exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - navigator.runAdAuction = sinon.stub(); - }); - - after(function() { - navigator.runAdAuction = origRunAdAuction; - }) - - afterEach(function () { - config.resetConfig(); - }); - - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({bidderSequence: 'fixed'}) - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - fledgeEnabled: true, - } - }); - - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - }); - }); - describe('sizeMapping', function () { let sandbox; beforeEach(function () { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1eecfac47a7..c0e48089b52 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,11 @@ -import {newBidder, registerBidder, preloadBidderMappingFile, storage, isValid} from 'src/adapters/bidderFactory.js'; +import { + newBidder, + registerBidder, + preloadBidderMappingFile, + storage, + isValid, + addComponentAuction +} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import { expect } from 'chai'; @@ -1212,16 +1219,27 @@ describe('validate bid response: ', function () { }; const fledgeAuctionConfig = { bidId: '1', + config: { + foo: 'bar' + } } describe('when response has FLEDGE auction config', function() { - let logInfoSpy; + let fledgeStub; - beforeEach(function () { - logInfoSpy = sinon.spy(utils, 'logInfo'); + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); }); - afterEach(function () { - logInfoSpy.restore(); + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); }); it('should unwrap bids', function() { @@ -1243,8 +1261,8 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.equal(true); + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); }) @@ -1257,8 +1275,8 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.be.true; + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(false); }) }) @@ -1266,6 +1284,10 @@ describe('validate bid response: ', function () { }); describe('preload mapping url hook', function() { + if (!FEATURES.VIDEO) { + return + } + let fakeTranslationServer; let getLocalStorageStub; let adapterManagerStub; diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 58bf8c9eb25..d4b3c8e583e 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -142,11 +142,11 @@ describe('storage manager', function() { const COOKIE = 'test-cookie'; const LS_KEY = 'test-localstorage'; - function mockBidderSettings() { + function mockBidderSettings(val) { return { get(bidder, key) { if (bidder === ALLOWED_BIDDER && key === ALLOW_KEY) { - return true; + return val; } else { return undefined; } @@ -157,39 +157,89 @@ describe('storage manager', function() { Object.entries({ disallowed: ['denied_bidder', false], allowed: [ALLOWED_BIDDER, true] - }).forEach(([test, [bidderCode, shouldWork]]) => { - describe(`for ${test} bidders`, () => { - let mgr; - - beforeEach(() => { - mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings()}); - }) - - afterEach(() => { - mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); - mgr.removeDataFromLocalStorage(LS_KEY); - }) - - const testDesc = (desc) => `should ${shouldWork ? '' : 'not'} ${desc}`; - - it(testDesc('allow cookies'), () => { - mgr.setCookie(COOKIE, 'value'); - expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('allow localStorage'), () => { - mgr.setDataInLocalStorage(LS_KEY, 'value'); - expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('report localStorage as available'), () => { - expect(mgr.hasLocalStorage()).to.equal(shouldWork); - }); - - it(testDesc('report cookies as available'), () => { - expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }).forEach(([t, [bidderCode, isBidderAllowed]]) => { + describe(`for ${t} bidders`, () => { + Object.entries({ + 'all': { + configValues: [ + true, + ['html5', 'cookie'] + ], + shouldWork: { + html5: true, + cookie: true + } + }, + 'none': { + configValues: [ + false, + [] + ], + shouldWork: { + html5: false, + cookie: false + } + }, + 'localStorage': { + configValues: [ + 'html5', + ['html5'] + ], + shouldWork: { + html5: true, + cookie: false + } + }, + 'cookies': { + configValues: [ + 'cookie', + ['cookie'] + ], + shouldWork: { + html5: false, + cookie: true + } + } + }).forEach(([t, {configValues, shouldWork: {cookie, html5}}]) => { + describe(`when ${t} is allowed`, () => { + configValues.forEach(configValue => describe(`storageAllowed = ${configValue}`, () => { + let mgr; + + beforeEach(() => { + mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); + }) + + afterEach(() => { + mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); + mgr.removeDataFromLocalStorage(LS_KEY); + }) + + function scenario(type, desc, fn) { + const shouldWork = isBidderAllowed && ({html5, cookie})[type]; + it(`${shouldWork ? '' : 'NOT'} ${desc}`, () => fn(shouldWork)); + } + + scenario('cookie', 'allow cookies', (shouldWork) => { + mgr.setCookie(COOKIE, 'value'); + expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'allow localStorage', (shouldWork) => { + mgr.setDataInLocalStorage(LS_KEY, 'value'); + expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'report localStorage as available', (shouldWork) => { + expect(mgr.hasLocalStorage()).to.equal(shouldWork); + }); + + scenario('cookie', 'report cookies as available', (shouldWork) => { + expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }); + })); + }); }); }); }); - }) + }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0867cb0e399..b069b13fc37 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2109,7 +2109,9 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids({ adUnits: fullAdUnit }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal( + FEATURES.VIDEO ? [[640, 480]] : [[300, 250]] + ); expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); @@ -2142,45 +2144,47 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let mixedAdUnit = [{ - code: 'test3', - bids: [], - sizes: [[300, 250], [300, 600]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[400, 350]] - }, - native: { - image: { - aspect_ratios: [200, 150], - required: true + if (FEATURES.VIDEO) { + let mixedAdUnit = [{ + code: 'test3', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 350]] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: mixedAdUnit - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: mixedAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let altVideoPlayerSize = [{ - code: 'test4', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [640, 480] + let altVideoPlayerSize = [{ + code: 'test4', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [640, 480] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: altVideoPlayerSize - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: altVideoPlayerSize + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + } }); it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { @@ -2285,41 +2289,43 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.')); - let badVideo1 = [{ - code: 'testb2', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: ['600x400'] + if (FEATURES.VIDEO) { + let badVideo1 = [{ + code: 'testb2', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['600x400'] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo1 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo1 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - let badVideo2 = [{ - code: 'testb3', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [['300', '200']] + let badVideo2 = [{ + code: 'testb3', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [['300', '200']] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo2 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo2 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + } if (FEATURES.NATIVE) { let badNativeImgSize = [{ @@ -3326,69 +3332,71 @@ describe('Unit: Prebid Module', function () { }); }); - describe('markWinningBidAsUsed', function () { - it('marks the bid object as used for the given adUnitCode/adId combination', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; - - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + if (FEATURES.VIDEO) { + describe('markWinningBidAsUsed', function () { + it('marks the bid object as used for the given adUnitCode/adId combination', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; + + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); + + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + it('try and mark the bid object, but fail because we supplied the wrong adId', function () { + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - it('try and mark the bid object, but fail because we supplied the wrong adId', function () { - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - it('marks the winning bid object as used for the given adUnitCode', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + it('marks the winning bid object as used for the given adUnitCode', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - it('marks a bid object as used for the given adId', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + it('marks a bid object as used for the given adId', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); }); - }); + } describe('setTargetingForAst', function () { let targeting; diff --git a/test/spec/unit/utils/promise_spec.js b/test/spec/unit/utils/promise_spec.js index a931b8bc9c4..bd8b0390b2e 100644 --- a/test/spec/unit/utils/promise_spec.js +++ b/test/spec/unit/utils/promise_spec.js @@ -19,94 +19,6 @@ describe('GreedyPromise', () => { }) }); - describe('unhandled rejections', () => { - let unhandled, done, stop; - - function reset(expectUnhandled) { - let pending = expectUnhandled; - let resolver; - unhandled.reset(); - unhandled.callsFake(() => { - pending--; - if (pending === 0) { - resolver(); - } - }) - done = new Promise((resolve) => { - resolver = resolve; - stop = function () { - if (expectUnhandled === 0) { - resolve() - } else { - resolver = resolve; - } - } - }) - } - - before(() => { - unhandled = sinon.stub(); - window.addEventListener('unhandledrejection', unhandled); - }); - - after(() => { - window.removeEventListener('unhandledrejection', unhandled); - }); - - function getUnhandledErrors() { - return unhandled.args.map((args) => args[0].reason); - } - - Object.entries({ - 'simple reject': [1, (P) => { P.reject('err'); stop() }], - 'caught reject': [0, (P) => P.reject('err').catch((e) => { stop(); return e })], - 'unhandled reject with finally': [1, (P) => P.reject('err').finally(() => 'finally')], - 'error handler that throws': [1, (P) => P.reject('err').catch((e) => { stop(); throw e })], - 'rejection handled later in the chain': [0, (P) => P.reject('err').then((v) => v).catch((e) => { stop(); return e })], - 'multiple errors in one chain': [1, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => { stop(); return P.reject(v) })], - 'multiple errors in one chain, all handled': [0, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => P.reject(v)).catch((e) => { stop(); return e })], - 'separate chains for rejection and handling': [1, (P) => { - const p = P.reject('err'); - p.catch((e) => { stop(); return e; }) - p.then((v) => v); - }], - 'separate rejections merged without handling': [2, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - p1.then(() => p2).finally(stop); - }], - 'separate rejections merged for handling': [0, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - P.all([p1, p2]).catch((e) => { stop(); return e }); - }], - // eslint-disable-next-line no-throw-literal - 'exception in resolver': [1, (P) => new P(() => { stop(); throw 'err'; })], - // eslint-disable-next-line no-throw-literal - 'exception in resolver, caught': [0, (P) => new P(() => { throw 'err' }).catch((e) => { stop(); return e })], - 'errors from nested promises': [1, (P) => new P((resolve) => setTimeout(() => { resolve(P.reject('err')); stop(); }))], - 'errors from nested promises, caught': [0, (P) => new P((resolve) => setTimeout(() => resolve(P.reject('err')))).catch((e) => { stop(); return e })], - }).forEach(([t, [expectUnhandled, op]]) => { - describe(`on ${t}`, () => { - it('should match vanilla Promises', () => { - let vanillaUnhandled; - reset(expectUnhandled); - op(Promise); - return done.then(() => { - vanillaUnhandled = getUnhandledErrors(); - reset(expectUnhandled); - op(GreedyPromise); - return done; - }).then(() => { - const actualUnhandled = getUnhandledErrors(); - expect(actualUnhandled.length).to.eql(expectUnhandled); - expect(actualUnhandled).to.eql(vanillaUnhandled); - }) - }) - }) - }); - }); - describe('idioms', () => { let makePromise, pendingFailure, pendingSuccess; @@ -172,9 +84,11 @@ describe('GreedyPromise', () => { 'chained Promise.reject': (P) => P.reject(pendingSuccess), 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), + 'empty Promise.all': (P) => P.all([]), 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), + 'empty Promise.allSettled': (P) => P.allSettled([]), 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index a13028c966a..c7c0b2eb329 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -72,6 +72,29 @@ describe('The video cache', function () { config.resetConfig(); }); + describe('cache.timeout', () => { + let getAjax, cb; + beforeEach(() => { + getAjax = sinon.stub().callsFake(() => sinon.stub()); + cb = sinon.stub(); + }); + + it('should be respected', () => { + config.setConfig({ + cache: { + timeout: 1 + } + }); + store([{ vastUrl: 'my-mock-url.com' }], cb, getAjax); + sinon.assert.calledWith(getAjax, 1); + }); + + it('should use default when not specified', () => { + store([], cb, getAjax); + sinon.assert.calledWith(getAjax, undefined); + }) + }); + it('should execute the callback with a successful result when store() is called', function () { const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; const callback = fakeServerCall( diff --git a/test/test_index.js b/test/test_index.js index 883f4d0590c..04d1412860b 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,3 +1,15 @@ + +[it, describe].forEach((ob) => { + ob.only = function () { + [ + 'describe.only and it.only are disabled unless you provide a single spec --file,', + 'because they can silently break the pipeline tests', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .only()') + } +}) + require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/);