diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index b6f4523bb50..03423a028b4 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,609 +1,238 @@ -import { - _each, - _map, - convertTypes, - deepAccess, - deepSetValue, - inIframe, - isArray, - parseSizesInput, - parseUrl -} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {includes} from '../src/polyfill.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', - 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; -const BIDDER_CODE = 'openx'; -const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.3'; - -const DEFAULT_CURRENCY = 'USD'; - -export const USER_ID_CODE_TO_QUERY_ARG = { - britepoolid: 'britepoolid', // BritePool ID - criteoId: 'criteoid', // CriteoID - fabrickId: 'nuestarid', // Fabrick ID by Nuestar - hadronId: 'audigentid', // Hadron ID from Audigent - id5id: 'id5id', // ID5 ID - idl_env: 'lre', // LiveRamp IdentityLink - IDP: 'zeotapid', // zeotapIdPlus ID+ - idxId: 'idxid', // idIDx, - intentIqId: 'intentiqid', // IntentIQ ID - lipb: 'lipbid', // LiveIntent ID - lotamePanoramaId: 'lotameid', // Lotame Panorama ID - merkleId: 'merkleid', // Merkle ID - netId: 'netid', // netID - parrableId: 'parrableid', // Parrable ID - pubcid: 'pubcid', // PubCommon ID - quantcastId: 'quantcastid', // Quantcast ID - tapadId: 'tapadid', // Tapad Id - tdid: 'ttduuid', // The Trade Desk Unified ID - uid2: 'uid2', // Unified ID 2.0 - admixerId: 'admixerid', // AdMixer ID - deepintentId: 'deepintentid', // DeepIntent ID - dmdId: 'dmdid', // DMD Marketing Corp ID - nextrollId: 'nextrollid', // NextRoll ID - novatiq: 'novatiqid', // Novatiq ID - mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID - dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid', // AMX RTB ID - kpuid: 'kpuid', // Kinesso ID - publinkId: 'publinkid', // Publisher Link - naveggId: 'naveggid', // Navegg ID - imuid: 'imuid', // IM-UID by Intimate Merger - adtelligentId: 'adtelligentid' // Adtelligent ID +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '2.0'; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams }; -export const spec = { - code: BIDDER_CODE, - gvlid: 69, - supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function (bidRequest) { - const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; - if (deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { - return !!bidRequest.params.unit || deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } +registerBidder(spec); - return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } - - let requests = []; - let [videoBids, bannerBids] = partitionByVideoBids(bidRequests); - - // build banner requests - if (bannerBids.length > 0) { - requests.push(buildOXBannerRequest(bannerBids, bidderRequest)); - } - // build video requests - if (videoBids.length > 0) { - videoBids.forEach(videoBid => { - requests.push(buildOXVideoRequest(videoBid, bidderRequest)) - }); + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + mergeDeep(imp, { + tagid: bidRequest.params.unit, + ext: { + divid: bidRequest.adUnitCode + } + }); + if (bidRequest.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bidRequest.params.customParams); } - - return requests; - }, - interpretResponse: function ({body: oxResponseObj}, serverRequest) { - let mediaType = getMediaTypeFromRequest(serverRequest); - - return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) - : createBannerBidResponses(oxResponseObj, serverRequest.payload); - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { - let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let url = deepAccess(responses, '0.body.ads.pixels') || - deepAccess(responses, '0.body.pixels') || - generateDefaultSyncUrl(gdprConsent, uspConsent); - - return [{ - type: pixelType, - url: url - }]; + if (bidRequest.params.customFloor && !imp.bidfloor) { + imp.bidfloor = bidRequest.params.customFloor; } + return imp; }, - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); - } -}; - -function generateDefaultSyncUrl(gdprConsent, uspConsent) { - let url = 'https://u.openx.net/w/1.0/pd'; - let queryParamStrings = []; - - if (gdprConsent) { - queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); - queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - - // CCPA - if (uspConsent) { - queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; -} - -function isVideoRequest(bidRequest) { - return (deepAccess(bidRequest, 'mediaTypes.video') && !deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; -} - -function createBannerBidResponses(oxResponseObj, {bids, startTime}) { - let adUnits = oxResponseObj.ads.ad; - let bidResponses = []; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - let adUnitIdx = parseInt(adUnit.idx, 10); - let bidResponse = {}; - - bidResponse.requestId = bids[adUnitIdx].bidId; - - if (adUnit.pub_rev) { - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - } else { - // No fill, do not add the bidresponse - continue; + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); } - let creative = adUnit.creative[0]; - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; + if (bid.params.doNotTrack) { + utils.deepSetValue(req, 'device.dnt', 1); } - bidResponse.creativeId = creative.id; - bidResponse.ad = adUnit.html; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = adUnit.currency; - - // additional fields to add - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); } - bidResponse.ts = adUnit.ts; - - bidResponse.meta = {}; - if (adUnit.brand_id) { - bidResponse.meta.brandId = adUnit.brand_id; + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); } - - if (adUnit.adomain && length(adUnit.adomain) > 0) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } else { - bidResponse.meta.advertiserDomains = []; + if (bid.params.test) { + req.test = 1 } - - if (adUnit.adv_id) { - bidResponse.meta.dspid = adUnit.adv_id; - } - - bidResponses.push(bidResponse); - } - return bidResponses; -} - -function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; + return req; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + const {ortbResponse} = context; + if (ortbResponse.ext && ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + // pass these from request to the responses for use in userSync + const {ortbRequest} = context; + if (ortbRequest.ext) { + if (ortbRequest.ext.delDomain) { + utils.deepSetValue(ortbResponse, 'ext.delDomain', ortbRequest.ext.delDomain); + } + if (ortbRequest.ext.platform) { + utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); + } } - body = tDoc.body; - - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; - } else { - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; - } - - return `${width}x${height}`; -} - -function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') -} - -function partitionByVideoBids(bidRequests) { - return bidRequests.reduce(function (acc, bid) { - // Fallback to banner ads if nothing specified - if (isVideoRequest(bid)) { - acc[0].push(bid); + const response = buildResponse(bidResponses, ortbResponse, context); + // TODO: we may want to standardize this and move fledge logic to ortbConverter + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } } else { - acc[1].push(bid); - } - return acc; - }, [[], []]); -} - -function getMediaTypeFromRequest(serverRequest) { - return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER; -} - -function buildCommonQueryParamsFromBids(bids, bidderRequest) { - const isInIframe = inIframe(); - let defaultParams; - - defaultParams = { - ju: bidderRequest.refererInfo.page, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isInIframe, - tz: new Date().getTimezoneOffset(), - tws: getViewportDimensions(isInIframe), - be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - dddid: _map(bids, bid => bid.transactionId).join(','), - nocache: new Date().getTime() - }; - - const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); - if (userAgentClientHints) { - defaultParams.sua = JSON.stringify(userAgentClientHints); - } - - const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); - if (userDataSegments.length > 0) { - defaultParams.sm = userDataSegments; - } - - const siteContentDataSegments = buildFpdQueryParams('site.content.data', bidderRequest.ortb2); - if (siteContentDataSegments.length > 0) { - defaultParams.scsm = siteContentDataSegments; - } - - if (bids[0].params.platform) { - defaultParams.ph = bids[0].params.platform; - } - - if (bidderRequest.gdprConsent) { - let gdprConsentConfig = bidderRequest.gdprConsent; - - if (gdprConsentConfig.consentString !== undefined) { - defaultParams.gdpr_consent = gdprConsentConfig.consentString; - } - - if (gdprConsentConfig.gdprApplies !== undefined) { - defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; + return response.bids } - - if (config.getConfig('consentManagement.cmpApi') === 'iab') { - defaultParams.x_gdpr_f = 1; - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - defaultParams.us_privacy = bidderRequest.uspConsent; - } - - // normalize publisher common id - if (deepAccess(bids[0], 'crumbs.pubcid')) { - deepSetValue(bids[0], 'userId.pubcid', deepAccess(bids[0], 'crumbs.pubcid')); - } - defaultParams = appendUserIdsToQueryParams(defaultParams, bids[0].userId); - - // supply chain support - if (bids[0].schain) { - defaultParams.schain = serializeSupplyChain(bids[0].schain); - } - - return defaultParams; -} - -function buildFpdQueryParams(fpdPath, ortb2) { - const firstPartyData = deepAccess(ortb2, fpdPath); - if (!Array.isArray(firstPartyData) || !firstPartyData.length) { - return ''; - } - const fpd = firstPartyData - .filter( - data => (Array.isArray(data.segment) && - data.segment.length > 0 && - data.name !== undefined && - data.name.length > 0) - ) - .reduce((acc, data) => { - const name = typeof data.ext === 'object' && data.ext.segtax ? `${data.name}/${data.ext.segtax}` : data.name; - acc[name] = (acc[name] || []).concat(data.segment.map(seg => seg.id)); - return acc; - }, {}) - return Object.keys(fpd) - .map((name, _) => name + ':' + fpd[name].join('|')) - .join(',') -} - -function appendUserIdsToQueryParams(queryParams, userIds) { - _each(userIds, (userIdObjectOrValue, userIdProviderKey) => { - const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; - - if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { - switch (userIdProviderKey) { - case 'merkleId': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'uid2': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'lipb': - queryParams[key] = userIdObjectOrValue.lipbid; - if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { - const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|'); - queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // enforce floors should always be in USD + // TODO: does it make sense that request.cur can be any currency, but request.imp[].bidfloorcur must be USD? + const floor = {}; + setBidFloor(floor, bidRequest, {...context, currency: 'USD'}); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; } - break; - case 'parrableId': - queryParams[key] = userIdObjectOrValue.eid; - break; - case 'id5id': - queryParams[key] = userIdObjectOrValue.uid; - break; - case 'novatiq': - queryParams[key] = userIdObjectOrValue.snowflake; - break; - default: - queryParams[key] = userIdObjectOrValue; + } } } - }); - - return queryParams; -} - -function serializeSupplyChain(supplyChain) { - return `${supplyChain.ver},${supplyChain.complete}!${serializeSupplyChainNodes(supplyChain.nodes)}`; -} - -function serializeSupplyChainNodes(supplyChainNodes) { - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + } +}); - return supplyChainNodes.map(supplyChainNode => { - return supplyChainNodePropertyOrder.map(property => supplyChainNode[property] || '') - .join(','); - }).join('!'); +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); } -function buildOXBannerRequest(bids, bidderRequest) { - let customParamsForAllBids = []; - let hasCustomParam = false; - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - let auids = _map(bids, bid => bid.params.unit); +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; - queryParams.aus = _map(bids, bid => parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divids = _map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); - // gpid - queryParams.aucs = _map(bids, function (bid) { - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return encodeURIComponent(gpid || '') - }).join(','); - - if (auids.some(auid => auid)) { - queryParams.auid = auids.join(','); - } - - if (bids.some(bid => bid.params.doNotTrack)) { - queryParams.ns = 1; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; } - if (config.getConfig('coppa') === true || bids.some(bid => bid.params.coppa)) { - queryParams.tfcd = 1; - } + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = _map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); - if (hasCustomParam) { - queryParams.tps = customParamsForAllBids.join(','); - } - - enrichQueryWithFloors(queryParams, BANNER, bids); - - let url = queryParams.ph - ? `https://u.openx.net/w/1.0/arj` - : `https://${bids[0].params.delDomain}/w/1.0/arj`; - - return { - method: 'GET', - url: url, - data: queryParams, - payload: {'bids': bids, 'startTime': new Date()} - }; + return requests; } -function buildOXVideoRequest(bid, bidderRequest) { - let oxVideoParams = generateVideoParameters(bid, bidderRequest); - let url = oxVideoParams.ph - ? `https://u.openx.net/v/1.0/avjp` - : `https://${bid.params.delDomain}/v/1.0/avjp`; +function createRequest(bidRequests, bidderRequest, mediaType) { return { - method: 'GET', - url: url, - data: oxVideoParams, - payload: {'bid': bid, 'startTime': new Date()} - }; -} - -function generateVideoParameters(bid, bidderRequest) { - const videoMediaType = deepAccess(bid, `mediaTypes.video`); - let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); - let oxVideoConfig = deepAccess(bid, 'params.video') || {}; - let context = deepAccess(bid, 'mediaTypes.video.context'); - let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - let width; - let height; - - // normalize config for video size - if (isArray(bid.sizes) && bid.sizes.length === 2 && !isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (isArray(bid.sizes) && 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 (isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); - } - - let openRtbParams = {w: width, h: height}; - - // legacy openrtb params could be in video, openrtb, or video.openrtb - let legacyParams = bid.params.video || bid.params.openrtb || {}; - if (legacyParams.openrtb) { - legacyParams = legacyParams.openrtb; - } - // support for video object or full openrtb object - if (isArray(legacyParams.imp)) { - legacyParams = legacyParams.imp[0].video; + method: 'POST', + url: config.getConfig('openxOrtbUrl') || REQUEST_URL, + data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) } - Object.keys(legacyParams) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = legacyParams[param]); - - // 5.0 openrtb video params - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = videoMediaType[param]); - - let openRtbReq = { - imp: [ - { - video: openRtbParams - } - ] - }; - - queryParams['openrtb'] = JSON.stringify(openRtbReq); - - queryParams.auid = bid.params.unit; - // override prebid config with openx config if available - queryParams.vwd = width || oxVideoConfig.vwd; - queryParams.vht = height || oxVideoConfig.vht; - - if (context === 'outstream') { - queryParams.vos = '101'; - } - - if (oxVideoConfig.mimes) { - queryParams.vmimes = oxVideoConfig.mimes; - } - - if (bid.params.test) { - queryParams.vtest = 1; - } - - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - queryParams.aucs = encodeURIComponent(gpid); - } - - // each video bid makes a separate request - enrichQueryWithFloors(queryParams, VIDEO, [bid]); - - return queryParams; } -function createVideoBidResponses(response, {bid, startTime}) { - let bidResponses = []; - - if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { - let vastQueryParams = parseUrl(response.vastUrl).search || {}; - let bidResponse = {}; - bidResponse.requestId = bid.bidId; - if (response.deal_id) { - bidResponse.dealId = response.deal_id; - } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = response.currency; - bidResponse.cpm = parseInt(response.pub_rev, 10) / 1000; - bidResponse.width = parseInt(response.width, 10); - bidResponse.height = parseInt(response.height, 10); - bidResponse.creativeId = response.adid; - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = VIDEO; +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} - // enrich adunit with vast parameters - response.ph = vastQueryParams.ph; - response.colo = vastQueryParams.colo; - response.ts = vastQueryParams.ts; +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} - bidResponses.push(bidResponse); +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = {nbr: 0}; } - - return bidResponses; + return converter.fromORTB({request: req.data, response: resp.body}); } -function enrichQueryWithFloors(queryParams, mediaType, bids) { - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - let floor = getBidFloor(bid, mediaType); - - if (floor) { - customFloorsForAllBids.push(floor); - hasCustomFloor = true; +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } } else { - customFloorsForAllBids.push(0); + queryParamStrings.push('ph=' + DEFAULT_PH) } - }); - if (hasCustomFloor) { - queryParams.aumfs = customFloorsForAllBids.join(','); + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; } } - -function getBidFloor(bidRequest, mediaType) { - let floorInfo = {}; - const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; - - if (typeof bidRequest.getFloor === 'function') { - floorInfo = bidRequest.getFloor({ - currency: currency, - mediaType: mediaType, - size: '*' - }); - } - let floor = floorInfo.floor || bidRequest.params.customFloor || 0; - - return Math.round(floor * 1000); // normalize to micro currency -} - -registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 0690bf6b4fc..a39aa1580cd 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -9,30 +9,31 @@ Maintainer: team-openx@openx.com # Description Module that connects to OpenX's demand sources. -Note there is an updated version of the OpenX bid adapter called openxOrtbBidAdapter. -Publishers are welcome to test the other adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +Note that this adapter mirrors openxOrtbBidAdapter and any updates must be +completed in both adapters. +openxOrtbBidAdapter will be removed in a future release and should not be used. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner -| Name | Scope | Type | Description | Example | -|---------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| `delDomain` ~~or `platform`~~** | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | -| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue

Note: OpenX suggests using the [Price Floor Module](https://docs.prebid.org/dev-docs/modules/floors.html) instead of customFloor. The Price Floor Module is prioritized over customFloor if both are present. | 1.50 | -| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true | -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true | - -** platform is deprecated. Please use delDomain instead. If you have any questions please contact your representative. +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video -| Name | Scope | Type | Description | Example | -|-------------|----------|--------------------|--------------------------------------------------------------|----------------------------------------------------------------| -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` | +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` + # Example ```javascript @@ -70,7 +71,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -79,10 +81,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md index 04ed32608cb..b5e1820021a 100644 --- a/modules/openxOrtbBidAdapter.md +++ b/modules/openxOrtbBidAdapter.md @@ -1,15 +1,17 @@ # Overview ``` -Module Name: OpenX OpenRTB Bidder Adapter +Module Name: OpenX Bidder Adapter Module Type: Bidder Adapter Maintainer: team-openx@openx.com ``` # Description +DEPRECATED. Use openxBidAdapter. -This is an updated version of the OpenX bid adapter which calls our new serving architecture. -Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +This adapter was originally an adapter used to test OpenX serving architecture changes. +This adapter now mirrors openxBidAdapter and this adapter will be removed in a future release. Please use openxBidAdapter. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 8fe220aa202..d8ea79ac698 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,204 +1,81 @@ import {expect} from 'chai'; -import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; +import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxOrtbBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from 'src/mediaTypes.js'; -import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; + +const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; -const URLBASE = '/w/1.0/arj'; -const URLBASEVIDEO = '/v/1.0/avjp'; - -describe('OpenxAdapter', function () { - const adapter = newBidder(spec); - - /** - * Type Definitions - */ - - /** - * @typedef {{ - * impression: string, - * inview: string, - * click: string - * }} - */ - let OxArjTracking; - /** - * @typedef {{ - * ads: { - * version: number, - * count: number, - * pixels: string, - * ad: Array - * } - * }} - */ - let OxArjResponse; - /** - * @typedef {{ - * adunitid: number, - * adid:number, - * type: string, - * htmlz: string, - * framed: number, - * is_fallback: number, - * ts: string, - * cpipc: number, - * pub_rev: string, - * tbd: ?string, - * adv_id: string, - * deal_id: string, - * auct_win_is_deal: number, - * brand_id: string, - * currency: string, - * idx: string, - * creative: Array - * }} - */ - let OxArjAdUnit; - /** - * @typedef {{ - * id: string, - * width: string, - * height: string, - * target: string, - * mime: string, - * media: string, - * tracking: OxArjTracking - * }} - */ - let OxArjCreative; - - // HELPER METHODS - /** - * @type {OxArjCreative} - */ - const DEFAULT_TEST_ARJ_CREATIVE = { - id: '0', - width: 'test-width', - height: 'test-height', - target: 'test-target', - mime: 'test-mime', - media: 'test-media', - tracking: { - impression: 'test-impression', - inview: 'test-inview', - click: 'test-click' - } + const request = { + ...defaults.request, + ...options }; - /** - * @type {OxArjAdUnit} - */ - const DEFAULT_TEST_ARJ_AD_UNIT = { - adunitid: 0, - type: 'test-type', - html: 'test-html', - framed: 0, - is_fallback: 0, - ts: 'test-ts', - tbd: 'NaN', - deal_id: undefined, - auct_win_is_deal: undefined, - cpipc: 0, - pub_rev: 'test-pub_rev', - adv_id: 'test-adv_id', - brand_id: 'test-brand_id', - currency: 'test-currency', - idx: '0', - creative: [DEFAULT_TEST_ARJ_CREATIVE] + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; }; - /** - * @type {OxArjResponse} - */ - const DEFAULT_ARJ_RESPONSE = { - ads: { - version: 0, - count: 1, - pixels: 'https://testpixels.net', - ad: [DEFAULT_TEST_ARJ_AD_UNIT] + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' } }; - // Sample bid requests + const request = { + ...defaults, + ...options + }; - const BANNER_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } }, - }]; - - const VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', + this.build = () => request; +}; - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; +describe('OpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); - const MULTI_FORMAT_BID_REQUESTS = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; + const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -206,7 +83,7 @@ describe('OpenxAdapter', function () { }); }); - describe('isBidRequestValid', function () { + describe('isBidRequestValid()', function () { describe('when request is for a banner ad', function () { let bannerBid; beforeEach(function () { @@ -259,8 +136,28 @@ describe('OpenxAdapter', function () { describe('when request is for a multiformat ad', function () { describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(MULTI_FORMAT_BID_REQUESTS[0])).to.equal(true); + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); }); }); }); @@ -349,1509 +246,1035 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); - - describe('and request config uses test', () => { - const videoBidWithTest = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain', - test: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - - let mockBidderRequest = {refererInfo: {}}; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); - }); - - it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { - const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); - expect(request[0].data.vtest).to.equal(1); - }); - }); }); }); - describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaTypes = BANNER_BID_REQUESTS_WITH_MEDIA_TYPES; - - const bidRequestsWithPlatform = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if platform is present', function () { - const request = spec.buildRequests(bidRequestsWithPlatform, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if both params present', function () { - const bidRequestsWithPlatformAndDelDomain = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; - it('should send the adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); - }); - - it('should send the gpids', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); - }); + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; - it('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidRequestsWithMediaTypes = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: '/adunit-code/test-path', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '22', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.auid).to.equal(`,${bidRequestsWithUnitIds[1].params.unit}`); - }); - - it('should not send any ad unit ids when none are defined', function () { - const bidRequestsWithoutUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + } }, { - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: 'adunit-code', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + playerSize: [640, 480] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' }]; - const request = spec.buildRequests(bidRequestsWithoutUnitIds, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('auid'); }); - it('should send out custom params on bids that have customParams specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] } - ); + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + it('should send bid request to openx url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal(REQUEST_URL); + expect(request[0].method).to.equal('POST'); + }); - expect(dataParams.tps).to.exist; - expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); - }); + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); + expect(request[0].data.ext.platformId).to.be.undefined; + }); - it('should send out custom bc parameter, if override is present', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'bc': 'hb_override' - } - } - ); + it('should send platform id, if available', function () { + bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + }); - expect(dataParams.bc).to.exist; - expect(dataParams.bc).to.equal('hb_override'); - }); + it('should send openx adunit codes', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); + expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + }); - it('should not send any consent management properties', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.gdpr).to.equal(undefined); - expect(request[0].data.gdpr_consent).to.equal(undefined); - expect(request[0].data.x_gdpr_f).to.equal(undefined); - }); + it('should send out custom params on bids that have customParams specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } + } + ); - describe('when there is a consent management framework', function () { - let bidRequests; - let mockConfig; - let bidderRequest; - const IAB_CONSENT_FRAMEWORK_CODE = 1; + mockBidderRequest.bids = [bidRequest]; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); + }) - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }, { - 'bidder': 'openx', - 'mediaTypes': { - video: { - playerSize: [640, 480] + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customFloor: 1.500 + } } - }, - 'params': { - 'unit': '12345678-video', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', + ); - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - }); + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); - afterEach(function () { - config.getConfig.restore(); - }); + context('with floors module', function () { + let adServerCurrencyStub; - describe('when us_privacy applies', function () { - beforeEach(function () { - bidderRequest = { - uspConsent: '1YYN', - refererInfo: {} - }; + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + afterEach(function () { + config.getConfig.restore(); }); - }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.us_privacy).to.equal('1YYN'); - expect(request[1].data.us_privacy).to.equal('1YYN'); - }); - }); + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); - describe('when us_privacy does not applies', function () { - beforeEach(function () { - bidderRequest = { - refererInfo: {} - }; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) }); - }); + }) + }) - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('us_privacy'); - expect(request[1].data).to.not.have.property('us_privacy'); - }); - }); + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; - describe('when GDPR applies', function () { beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - refererInfo: {} - }; + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('ortb2.site should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } }); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(1); - expect(request[1].data.gdpr).to.equal(1); + it('ortb2.user should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + user: { + yob: 1985 + } + } + }); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - describe('when GDPR does not apply', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: false - }, - refererInfo: {} - }; - - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); }); - }); - it('should not send a signal to specify that GDPR does not apply to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(0); - expect(request[1].data.gdpr).to.equal(0); - }); + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - describe('when GDPR consent has undefined data', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true - }, - refererInfo: {} - }; + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should not send a signal to specify whether GDPR applies to this request, when GDPR application is undefined', function () { - delete bidderRequest.gdprConsent.gdprApplies; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr'); - expect(request[1].data).to.not.have.property('gdpr'); - }); + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.gdprConsent.consentString; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr_consent'); - expect(request[1].data).to.not.have.property('gdpr_consent'); + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); }); - it('should not send the consent management framework code, when format is undefined', function () { - delete mockConfig.consentManagement.cmpApi; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('x_gdpr_f'); - expect(request[1].data).to.not.have.property('x_gdpr_f'); + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); }); }); - }); - - it('should not send a coppa query param when there are no coppa param settings in the bid requests', function () { - const bidRequestsWithoutCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutCoppa, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('tfcd'); - }); - - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { - const bidRequestsWithCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - coppa: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithCoppa, mockBidderRequest); - expect(request[0].data.tfcd).to.equal(1); - }); - it('should not send a "no segmentation" flag there no DoNotTrack setting that is set to true', function () { - const bidRequestsWithoutDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutDnt, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('ns'); - }); + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; - it('should send a "no segmentation" flag there is any DoNotTrack setting that is set to true', function () { - const bidRequestsWithDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - doNotTrack: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithDnt, mockBidderRequest); - expect(request[0].data.ns).to.equal(1); - }); - - describe('when schain is provided', function () { - let bidRequests; - let schainConfig; - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - - beforeEach(function () { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - // omitted ext + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - { - 'asi': 'exchange2.com', - 'sid': 'abcd', - 'hp': 1, - 'rid': 'bid-request-2', - // name field missing - 'domain': 'intermediary.com' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } }, - { - 'asi': 'exchange3.com', - 'sid': '4321', - 'hp': 1, - // request id - // name field missing - 'domain': 'intermediary-2.com' - } - ] - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - bidRequests = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1', - 'schain': schainConfig - }]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - it('should send a schain parameter with the proper delimiter symbols', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - const numNodes = schainConfig.nodes.length; + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; - // each node will have a ! to denote beginning of a new node - expect(dataParams.schain.match(/!/g).length).to.equal(numNodes); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - // 1 comma in the front for version - // 5 commas per node - expect(dataParams.schain.match(/,/g).length).to.equal(numNodes * 5 + 1); - }); + afterEach(function () { + config.getConfig.restore(); + }); - it('should send a schain with the right version', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let version = serializedSupplyChain.shift().split(',')[0]; + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); - expect(version).to.equal(bidRequests[0].schain.ver); - }); + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; + }); + }); - it('should send a schain with the right complete value', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let isComplete = serializedSupplyChain.shift().split(',')[1]; + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; - expect(isComplete).to.equal(String(bidRequests[0].schain.complete)); - }); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - it('should send all available params in the right order', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - serializedSupplyChain.shift(); + afterEach(function () { + config.getConfig.restore(); + }); - serializedSupplyChain.forEach((serializedNode, nodeIndex) => { - let nodeProperties = serializedNode.split(','); + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); - nodeProperties.forEach((nodeProperty, propertyIndex) => { - let node = schainConfig.nodes[nodeIndex]; - let key = supplyChainNodePropertyOrder[propertyIndex]; + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(nodeProperty).to.equal(node[key] ? String(node[key]) : '', - `expected node '${nodeIndex}' property '${nodeProperty}' to key '${key}' to be the same value`) + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - }); - }); - }); - describe('when there are userid providers', function () { - const EXAMPLE_DATA_BY_ATTR = { - britepoolid: '1111-britepoolid', - criteoId: '1111-criteoId', - fabrickId: '1111-fabrickid', - haloId: '1111-haloid', - id5id: {uid: '1111-id5id'}, - idl_env: '1111-idl_env', - IDP: '1111-zeotap-idplusid', - idxId: '1111-idxid', - intentIqId: '1111-intentiqid', - lipb: {lipbid: '1111-lipb'}, - lotamePanoramaId: '1111-lotameid', - merkleId: {id: '1111-merkleid'}, - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, - pubcid: '1111-pubcid', - quantcastId: '1111-quantcastid', - tapadId: '111-tapadid', - tdid: '1111-tdid', - uid2: {id: '1111-uid2'}, - novatiq: {snowflake: '1111-novatiqid'}, - admixerId: '1111-admixerid', - deepintentId: '1111-deepintentid', - dmdId: '111-dmdid', - nextrollId: '1111-nextrollid', - mwOpenLinkId: '1111-mwopenlinkid', - dapId: '1111-dapId', - amxId: '1111-amxid', - kpuid: '1111-kpuid', - publinkId: '1111-publinkid', - naveggId: '1111-naveggid', - imuid: '1111-imuid', - adtelligentId: '1111-adtelligentid' - }; - - // generates the same set of tests for each id provider - utils._each(USER_ID_CODE_TO_QUERY_ARG, (userIdQueryArg, userIdProviderKey) => { - describe(`with userId attribute: ${userIdProviderKey}`, function () { - it(`should not send a ${userIdQueryArg} query param when there is no userId.${userIdProviderKey} defined in the bid requests`, function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys(userIdQueryArg); + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); }); - it(`should send a ${userIdQueryArg} query param when userId.${userIdProviderKey} is defined in the bid requests`, function () { - const bidRequestsWithUserId = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - // enrich bid request with userId key/value - bidRequestsWithUserId[0].userId[userIdProviderKey] = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - - let userIdValue; - // handle cases where userId key refers to an object - switch (userIdProviderKey) { - case 'merkleId': - userIdValue = EXAMPLE_DATA_BY_ATTR.merkleId.id; - break; - case 'uid2': - userIdValue = EXAMPLE_DATA_BY_ATTR.uid2.id; - break; - case 'lipb': - userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; - break; - case 'parrableId': - userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; - break; - case 'id5id': - userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; - break; - case 'novatiq': - userIdValue = EXAMPLE_DATA_BY_ATTR.novatiq.snowflake; - break; - default: - userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - } + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(request[0].data[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]]).to.equal(userIdValue); + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); }); }); - }); - - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - context('with floors module', function () { - let adServerCurrencyStub; - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send out floors on bids', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 9.99 - } - } - } - ); - - const bidRequest2 = Object.assign({}, - bidRequestsWithMediaTypes[1], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 18.881 - } - } - } - ); + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; - const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); - const dataParams = request[0].data; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('9990,18881'); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + after(function () { + config.getConfig.restore() }); - }) - }) - }); - - describe('buildRequests for video', function () { - const bidRequestsWithMediaTypes = VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES; - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); - expect(request[0].method).to.equal('GET'); - }); - it('should have the correct parameters', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.auid).to.equal('12345678'); - expect(dataParams.vht).to.equal(480); - expect(dataParams.vwd).to.equal(640); - expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); - }); - - it('shouldn\'t have the test parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.vtest).to.be.undefined; - }); - - it('should send a bc parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.bc).to.have.string('hb_pb'); - }); - - describe('when using the video param', function () { - let videoBidRequest; - let mockBidderRequest = {refererInfo: {}}; - - beforeEach(function () { - videoBidRequest = { - 'bidder': 'openx', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [640, 480] - } - }, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - mockBidderRequest = {refererInfo: {}}; - }); - - it('should not allow you to set a url', function () { - videoBidRequest.params.video = { - url: 'test-url' - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.url).to.be.undefined; - }); - - it('should not allow you to override the javascript url', function () { - let myUrl = 'my-url'; - videoBidRequest.params.video = { - ju: myUrl - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openrtb video params', function () { - it('should parse legacy params.video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + context('do not track (DNT)', function() { + let doNotTrackStub; - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); }); - - it('should parse legacy params.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.openrtb = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + afterEach(function() { + doNotTrackStub.restore(); }); - it('should parse legacy params.video', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(1); }); - it('should parse legacy params.video as full openrtb', function () { - let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); - it('should parse legacy video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); + }); - it('should omit filtered values for legacy', function () { - let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + bidRequests = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; }); - it('should parse mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minduration = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); }); - it('should filter mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minnothing = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); }); - it("should use the bidRequest's playerSize", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - - expect(openRtbRequestParams.imp[0].video.w).to.equal(640); - expect(openRtbRequestParams.imp[0].video.h).to.equal(480); + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); }); }); - }); - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], + context('when there are userid providers', function () { + const userIdAsEids = [ { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); + ]; - context('with floors module', function () { - let adServerCurrencyStub; - function makeBidWithFloorInfo(floorInfo) { - return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), - { - getFloor: () => { - return floorInfo; + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } - }); - } - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); }); - afterEach(function () { - config.getConfig.restore(); + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); }); + }); - it('should send out floors on bids', function () { - const floors = [9.99, 18.881]; - const bidRequests = floors.map(floor => { - return makeBidWithFloorInfo({ - currency: 'AUS', - floor: floor - }); + context('FLEDGE', function() { + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, { + ...mockBidderRequest, + fledgeEnabled: true }); - const request = spec.buildRequests(bidRequests, mockBidderRequest); - - expect(request[0].data.aumfs).to.exist; - expect(request[0].data.aumfs).to.equal('9990'); - expect(request[1].data.aumfs).to.exist; - expect(request[1].data.aumfs).to.equal('18881'); + expect(request[0].data.imp[0].ext.ae).to.equal(2); }); + }); + }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + 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('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); }); - }) - }) + }); + } }); - describe('buildRequest for multi-format ad', function () { - const multiformatBid = MULTI_FORMAT_BID_REQUESTS[0]; - let mockBidderRequest = {refererInfo: {}}; + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; - it('should default to a banner request', function () { - const request = spec.buildRequests([multiformatBid], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); - }); - }); - - describe('buildRequests for all kinds of ads', function () { - utils._each({ - banner: BANNER_BID_REQUESTS_WITH_MEDIA_TYPES[0], - video: VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES[0], - multi: MULTI_FORMAT_BID_REQUESTS[0] - }, (bidRequest, name) => { - describe('with segments', function () { - const TESTS = [ - { - name: 'should send proprietary segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from both ortb2.site.content.data and ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - expect: { - sm: 'dmp1/4:foo|bar,dmp2:baz', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' - }, - }, - { - name: 'should combine same provider segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should combine same provider segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should not send any segment data if first party config is incomplete', - config: { - ortb2: { - user: { - data: [ - {name: 'provider-with-no-segments'}, - {segment: [{id: 'segments-with-no-provider'}]}, - {}, - ] - } - } - } + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send first party data segments and liveintent segments from request', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: { - sm: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - { - name: 'should send just liveintent segment from request if no first party config', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: {sm: 'liveintent:l1|l2'}, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when no seatbid in response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send nothing if lipb section does not contain segments', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - }, - }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - ]; - utils._each(TESTS, (t) => { - context('in ortb2.user.data', function () { - let bidRequests; - beforeEach(function () { - bidRequests = [{...bidRequest, ...t.request}]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - const mockBidderRequest = {refererInfo: {}, ortb2: t.config.ortb2}; - it(`${t.name} for type ${name}`, function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest) - expect(request.length).to.equal(1); - if (t.expect) { - for (const key in t.expect) { - expect(request[0].data[key]).to.exist; - expect(request[0].data[key]).to.equal(t.expect[key]); - } - } else { - expect(request[0].data.sm).to.not.exist; - expect(request[0].data.scsm).to.not.exist; - } - }); - }); - }); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {ext: {}, id: 'test-bid-id'}; + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - describe('with user agent client hints', function () { - it('should add json query param sua with BidRequest.device.sua if available', function () { - const bidderRequestWithUserAgentClientHints = { refererInfo: {}, - ortb2: { - device: { - sua: { - source: 2, - platform: { - brand: 'macOS', - version: [ '12', '4', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - }], - mobile: 0, - model: 'Pro', - bitness: '64', - architecture: 'x86' - } - } - }}; - - let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); - expect(request[0].data.sua).to.exist; - const payload = JSON.parse(request[0].data.sua); - expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); - const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; - request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.sua).to.not.exist; - }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); }); }); - }) - describe('interpretResponse for banner ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - afterEach(function () { - userSync.registerSync.restore(); + bidResponse = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); }); - describe('when there is a standard response', function () { - const creativeOverride = { - id: 234, - width: '300', - height: '250', - tracking: { - impression: 'https://openx-d.openx.net/v/1.0/ri?ts=ts' + const SAMPLE_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', + paf: { + content_id: 'paf_content_id' + } + } + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} } - }; - - const adUnitOverride = { - ts: 'test-1234567890-ts', - idx: '0', - currency: 'USD', - pub_rev: '10000', - html: '
OpenX Ad
' - }; - let adUnit; - let bidResponse; - - let bid; - let bidRequest; - let bidRequestConfigs; + } + }; + context('when there is a response, the common response properties', function () { beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); - adUnit = mockAdUnit(adUnitOverride, creativeOverride); - bidResponse = mockArjResponse(undefined, [adUnit]); bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); it('should return a price', function () { - expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000); + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); }); it('should return a request id', function () { - expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId); + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); }); it('should return width and height for the creative', function () { - expect(bid.width).to.equal(creativeOverride.width); - expect(bid.height).to.equal(creativeOverride.height); + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); }); it('should return a creativeId', function () { - expect(bid.creativeId).to.equal(creativeOverride.id); + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); }); it('should return an ad', function () { - expect(bid.ad).to.equal(adUnitOverride.html); + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); }); it('should have a time-to-live of 5 minutes', function () { @@ -1863,415 +1286,293 @@ describe('OpenxAdapter', function () { }); it('should return a currency', function () { - expect(bid.currency).to.equal(adUnitOverride.currency); - }); - - it('should return a transaction state', function () { - expect(bid.ts).to.equal(adUnitOverride.ts); + expect(bid.currency).to.equal(bidResponse.cur); }); it('should return a brand ID', function () { - expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); }); - it('should return an adomain', function () { - expect(bid.meta.advertiserDomains).to.deep.equal([]); + it('should return a dsp ID', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); }); - it('should return a dsp ID', function () { - expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); + it('should return a buyer ID', function () { + expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); }); - }); - describe('when there is a deal', function () { - const adUnitOverride = { - deal_id: 'ox-1000' - }; - let adUnit; - let bidResponse; + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); + }); - let bid; - let bidRequestConfigs; - let bidRequest; + it('should return paf fields', function () { + const paf = { + transmission: {version: '12'}, + content_id: 'paf_content_id' + } + expect(bid.meta.paf).to.deep.equal(paf); + }); + }); + context('when there is more than one response', () => { + let bids; beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); + bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); + bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - adUnit = mockAdUnit(adUnitOverride); - bidResponse = mockArjResponse(null, [adUnit]); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - mockArjResponse(); + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should return a deal id', function () { - expect(bid.dealId).to.equal(adUnitOverride.deal_id); + it('should not confuse paf content_id', () => { + expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); }); - }); - - describe('when there is no bids in the response', function () { - let bidRequest; - let bidRequestConfigs; + }) + context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' }]; - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - }); - - it('handles nobid responses', function () { - const bidResponse = { - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'https://testpixels.net', - 'ad': [] - } + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' }; - const result = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(result.length).to.equal(0); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); - }); - describe('when adunits return out of order', function () { - const bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[100, 111]] - } - }, - bidId: 'test-bid-request-id-1', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[200, 222]] - } - }, - bidId: 'test-bid-request-id-2', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 333]] - } - }, - 'bidId': 'test-bid-request-id-3', - 'bidderRequestId': 'test-request-1', - 'auctionId': 'test-auction-id-1' - }]; - const bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequests, 'startTime': new Date()} - }; - - let outOfOrderAdunits = [ - mockAdUnit({ - idx: '1' - }, { - width: bidRequests[1].mediaTypes.banner.sizes[0][0], - height: bidRequests[1].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '2' - }, { - width: bidRequests[2].mediaTypes.banner.sizes[0][0], - height: bidRequests[2].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '0' - }, { - width: bidRequests[0].mediaTypes.banner.sizes[0][0], - height: bidRequests[0].mediaTypes.banner.sizes[0][1] - }) - ]; - - let bidResponse = mockArjResponse(undefined, outOfOrderAdunits); - - it('should return map adunits back to the proper request', function () { - const bids = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(bids[0].requestId).to.equal(bidRequests[1].bidId); - expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]); - expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]); - expect(bids[1].requestId).to.equal(bidRequests[2].bidId); - expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]); - expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]); - expect(bids[2].requestId).to.equal(bidRequests[0].bidId); - expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]); + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); }); }); - }); - - describe('interpretResponse for video ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); - afterEach(function () { - userSync.registerSync.restore(); - }); - - const bidsWithMediaTypes = [{ - 'bidder': 'openx', - 'mediaTypes': {video: {}}, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidsWithMediaType = [{ - 'bidder': 'openx', - 'mediaType': 'video', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidRequestsWithMediaTypes = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaTypes[0], 'startTime': new Date()} - }; - const bidRequestsWithMediaType = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} - }; - const bidResponse = { - 'pub_rev': '1000', - 'width': '640', - 'height': '480', - 'adid': '5678', - 'currency': 'AUD', - 'vastUrl': 'https://testvast.com', - 'pixels': 'https://testpixels.net' - }; + if (FEATURES.VIDEO) { + 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]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + 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 correct bid response with MediaTypes', function () { - const expectedResponse = { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'AUD' - }; - - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result[0]).to.eql(expectedResponse); - }); + 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 correct bid response with MediaType', function () { - const expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': '640', - 'height': '480', - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'USD' - } - ]; + 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]; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); - }); + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + } - it('should return correct bid response with MediaType and deal_id', function () { - const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; - const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); - const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); - expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); - }); + context('when the response contains FLEDGE interest groups config', function() { + let response; - it('should handle nobid responses for bidRequests with MediaTypes', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result.length).to.equal(0); - }); + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('fledgeEnabled') + .returns(true); - it('should handle nobid responses for bidRequests with MediaType', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(result.length).to.equal(0); - }); - }); + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - describe('user sync', function () { - const syncUrl = 'https://testpixels.net'; + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS', + ext: { + fledge_auction_configs: { + 'test-bid-id': { + seller: 'codinginadtech.com', + interestGroupBuyers: ['somedomain.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'somedomain.com': { + base_bid_micros: 0.1, + disallowed_advertiser_ids: [ + '1234', + '2345' + ], + multiplier: 1.3, + use_bid_multiplier: true, + win_reporting_id: '1234567asdf' + } + } + } + } + } + }; - describe('iframe sync', function () { - it('should register the pixel iframe from banner ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should register the pixel iframe from video ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + afterEach(function () { + config.getConfig.restore(); }); - it('should register the default iframe if no pixels available', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://u.openx.net/w/1.0/pd'}]); + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); }); }); + }); - describe('pixel sync', function () { - it('should register the image pixel from banner ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + describe('user sync', function () { + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); + }); - it('should register the image pixel from video ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {delDomain: 'www.url.com'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); + }); - it('should register the default image pixel if no pixels available', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'image', url: 'https://u.openx.net/w/1.0/pd'}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {platform: 'abc'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); + }); + + it('when iframe sync is allowed, it should register an iframe sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); it('should prioritize iframe over image for user sync', function () { let syncs = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] + [] ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); describe('when gdpr applies', function () { let gdprConsent; let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; beforeEach(() => { gdprConsent = { - consentString: 'test-gdpr-consent-string', + consentString, gdprApplies: true }; - gdprPixelUrl = 'https://testpixels.net?gdpr=1&gdpr_consent=gdpr-pixel-consent' + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; }); it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: gdprPixelUrl}}}], - gdprConsent - ); - - expect(url).to.have.string('gdpr_consent=gdpr-pixel-consent'); - expect(url).to.have.string('gdpr=1'); - }); - - it('when there is no response, it should append gdpr query params', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], gdprConsent ); - expect(url).to.have.string('gdpr_consent=test-gdpr-consent-string'); - expect(url).to.have.string('gdpr=1'); + + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); }); it('should not send signals if no consent object is available', function () { @@ -2287,28 +1588,19 @@ describe('OpenxAdapter', function () { describe('when ccpa applies', function () { let usPrivacyConsent; let uspPixelUrl; + const privacyString = 'TEST'; beforeEach(() => { usPrivacyConsent = 'TEST'; - uspPixelUrl = 'https://testpixels.net?us_privacy=AAAA' + uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` }); - it('when there is a response, it should send the us privacy string from the response, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: uspPixelUrl}}}], - undefined, - usPrivacyConsent - ); - - expect(url).to.have.string('us_privacy=AAAA'); - }); - it('when there is no response, it send have the us privacy string', () => { + it('should send the us privacy string, ', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], undefined, usPrivacyConsent ); - expect(url).to.have.string(`us_privacy=${usPrivacyConsent}`); + expect(url).to.have.string(`us_privacy=${privacyString}`); }); it('should not send signals if no consent string is available', function () { @@ -2320,75 +1612,4 @@ describe('OpenxAdapter', function () { }); }); }); - - /** - * Makes sure the override object does not introduce - * new fields against the contract - * - * This does a shallow check in order to make key checking simple - * with respect to what a helper handles. For helpers that have - * nested fields, either check your design on maybe breaking it up - * to smaller, manageable pieces - * - * OR just call this on your nth level field if necessary. - * - * @param {Object} override Object with keys that overrides the default - * @param {Object} contract Original object contains the default fields - * @param {string} typeName Name of the type we're checking for error messages - * @throws {AssertionError} - */ - function overrideKeyCheck(override, contract, typeName) { - expect(contract).to.include.all.keys(Object.keys(override)); - } - - /** - * Creates a mock ArjResponse - * @param {OxArjResponse=} response - * @param {Array=} adUnits - * @throws {AssertionError} - * @return {OxArjResponse} - */ - function mockArjResponse(response, adUnits = []) { - let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE); - - if (response) { - overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse'); - overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse'); - Object.assign(mockedArjResponse, response); - } - - if (adUnits.length) { - mockedArjResponse.ads.count = adUnits.length; - mockedArjResponse.ads.ad = adUnits.map((adUnit) => { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - }); - } - - return mockedArjResponse; - } - - /** - * Creates a mock ArjAdUnit - * @param {OxArjAdUnit=} adUnit - * @param {OxArjCreative=} creative - * @throws {AssertionError} - * @return {OxArjAdUnit} - */ - function mockAdUnit(adUnit, creative) { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - - let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - - if (creative) { - overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE); - if (creative.tracking) { - overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative'); - } - Object.assign(mockedAdUnit.creative[0], creative); - } - - return mockedAdUnit; - } -}) -; +}); diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 951399457c3..d8ea79ac698 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -1096,45 +1096,6 @@ describe('OpenxRtbAdapter', function () { }); }); } - - it.skip('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - bidder: 'openx', - params: { - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - transactionId: 'test-transaction-id-2' - }]; - mockBidderRequest.bids = bidRequestsWithUnitIds; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithUnitIds[1].params.unit); - expect(request[0].data.imp[1].ext.divid).to.equal(bidRequestsWithUnitIds[1].params.adUnitCode); - }); }); describe('interpretResponse()', function () {