diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 81872100cd1..a02812a1608 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,362 +1,410 @@ -// jshint esversion: 6, es3: false, node: true -'use strict'; - -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {NATIVE, BANNER, VIDEO} from '../src/mediaTypes.js'; -import { - mergeDeep, - _map, - deepAccess, - getDNT, - parseSizesInput, - deepSetValue, - isStr, - isArray, - isPlainObject, - parseUrl, - replaceAuctionPrice, triggerPixel -} from '../src/utils.js'; -import {config} from '../src/config.js'; - -const { getConfig } = config; - -const BIDDER_CODE = 'adxcg'; -const SECURE_BID_URL = 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'; - -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 1, - type: 12, - name: 'data' - } -}; +import { logWarn, isStr, deepAccess, inIframe, checkCookieSupport, timestamp, getBidIdParameter, parseSizesInput, buildUrl, logMessage, isArray, deepSetValue, isPlainObject, triggerPixel, replaceAuctionPrice, isFn } from '../src/utils.js'; +import {config} from '../src/config.js' +import {registerBidder} from '../src/adapters/bidderFactory.js' +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js' +import includes from 'core-js-pure/features/array/includes.js' + +/** + * Adapter for requesting bids from adxcg.net + * updated to latest prebid repo on 2017.10.20 + * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module + * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters + * updated to fix native support for image width/height and icon 2019.03.17 + * updated support for userid - pubcid,ttid 2019.05.28 + * updated to support prebid 3.0 - remove non https, move to banner.xx.sizes, remove utils.getTopWindowLocation,remove utils.getTopWindowUrl(),remove utils.getTopWindowReferrer() + * updated to support prebid 4.0 - standardized video params, updated video validation, add onBidWon, onTimeOut, use standardized getFloor + */ + +const BIDDER_CODE = 'adxcg' +const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE] +const SOURCE = 'pbjs10' +const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks'] +const USER_PARAMS_AUCTION = ['forcedDspIds', 'forcedCampaignIds', 'forcedCreativeIds', 'gender', 'dnt', 'language'] +const USER_PARAMS_BID = ['lineparam1', 'lineparam2', 'lineparam3'] +const BIDADAPTERVERSION = 'r20210330PB40' +const DEFAULT_MIN_FLOOR = 0; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], - isBidRequestValid: (bid) => { - const params = bid.params || {}; - const { adzoneid } = params; - return !!(adzoneid); - }, - buildRequests: (validBidRequests, bidderRequest) => { - let app, site; - - const commonFpd = getConfig('ortb2') || {}; - let { user } = commonFpd; + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!bid || !bid.params) { + logWarn(BIDDER_CODE + ': Missing bid parameters'); + return false + } - if (typeof getConfig('app') === 'object') { - app = getConfig('app') || {}; - if (commonFpd.app) { - mergeDeep(app, commonFpd.app); - } - } else { - site = getConfig('site') || {}; - if (commonFpd.site) { - mergeDeep(site, commonFpd.site); - } + if (!isStr(bid.params.adzoneid)) { + logWarn(BIDDER_CODE + ': adzoneid must be specified as a string'); + return false + } - if (!site.page) { - site.page = bidderRequest.refererInfo.referer; - site.domain = parseUrl(bidderRequest.refererInfo.referer).hostname; + if (isBannerRequest(bid)) { + const banneroAdUnit = deepAccess(bid, 'mediaTypes.banner'); + if (!banneroAdUnit.sizes) { + logWarn(BIDDER_CODE + ': banner sizes must be specified'); + return false; } } - const device = getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; - device.ua = device.ua || navigator.userAgent; - device.dnt = getDNT() ? 1 : 0; - device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - - const tid = validBidRequests[0].transactionId; - const test = setOnAny(validBidRequests, 'params.test'); - const currency = getConfig('currency.adServerCurrency'); - const cur = currency && [ currency ]; - const eids = setOnAny(validBidRequests, 'userIdAsEids'); - const schain = setOnAny(validBidRequests, 'schain'); - - const imp = validBidRequests.map((bid, id) => { - const floorInfo = bid.getFloor ? bid.getFloor({ - currency: currency || 'USD' - }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; - const { adzoneid } = bid.params; - - const imp = { - id: id + 1, - tagid: adzoneid, - secure: 1, - bidfloor, - bidfloorcur, - ext: { - } - }; - - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } + if (isVideoRequest(bid)) { + // prebid 4.0 use standardized Video parameters + const videoAdUnit = deepAccess(bid, 'mediaTypes.video'); - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } - }).filter(Boolean); - - if (assets.length) { - imp.native = { - request: JSON.stringify({assets: assets}) - }; + if (!Array.isArray(videoAdUnit.playerSize)) { + logWarn(BIDDER_CODE + ': video playerSize must be an array of integers'); + return false; } - const bannerParams = deepAccess(bid, 'mediaTypes.banner'); - - if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); + if (!videoAdUnit.context) { + logWarn(BIDDER_CODE + ': video context must be specified'); + return false; + } - imp.banner = { - format - }; + if (!Array.isArray(videoAdUnit.mimes) || videoAdUnit.mimes.length === 0) { + logWarn(BIDDER_CODE + ': video mimes must be an array of strings'); + return false; } - const videoParams = deepAccess(bid, 'mediaTypes.video'); - if (videoParams) { - imp.video = videoParams; + if (!Array.isArray(videoAdUnit.protocols) || videoAdUnit.protocols.length === 0) { + logWarn(BIDDER_CODE + ': video protocols must be an array of integers'); + return false; } + } - return imp; - }); + return true + }, - const request = { - id: bidderRequest.auctionId, - site, - app, - user, - geo: { utcoffset: new Date().getTimezoneOffset() }, - device, - source: { tid, fd: 1 }, - ext: { - prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } - } - }, - cur, - imp + /** + * Make a server request from the list of BidRequests. + * + * an array of validBidRequests + * Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let dt = new Date(); + let ratio = window.devicePixelRatio || 1; + let iobavailable = window && window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && 'intersectionRatio' in window.IntersectionObserverEntry.prototype + + let bt = config.getConfig('bidderTimeout'); + if (window.PREBID_TIMEOUT) { + bt = Math.min(window.PREBID_TIMEOUT, bt); + } + + let referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + let page = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + + // add common parameters + let beaconParams = { + renderformat: 'javascript', + ver: BIDADAPTERVERSION, + secure: '1', + source: SOURCE, + uw: window.screen.width, + uh: window.screen.height, + dpr: ratio, + bt: bt, + isinframe: inIframe(), + cookies: checkCookieSupport() ? '1' : '0', + tz: dt.getTimezoneOffset(), + dt: timestamp(), + iob: iobavailable ? '1' : '0', + pbjs: '$prebid.version$', + rndid: Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000, + ref: encodeURIComponent(referrer), + url: encodeURIComponent(page) }; - if (test) { - request.is_debug = !!test; - request.test = 1; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + beaconParams.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0'; + beaconParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (isStr(deepAccess(validBidRequests, '0.userId.pubcid'))) { + beaconParams.pubcid = validBidRequests[0].userId.pubcid; } - if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + + if (isStr(deepAccess(validBidRequests, '0.userId.tdid'))) { + beaconParams.tdid = validBidRequests[0].userId.tdid; } - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + if (isStr(deepAccess(validBidRequests, '0.userId.id5id.uid'))) { + beaconParams.id5id = validBidRequests[0].userId.id5id.uid; } - if (eids) { - deepSetValue(request, 'user.ext.eids', eids); + if (isStr(deepAccess(validBidRequests, '0.userId.idl_env'))) { + beaconParams.idl_env = validBidRequests[0].userId.idl_env; } - if (schain) { - deepSetValue(request, 'source.ext.schain', schain); + let biddercustom = config.getConfig(BIDDER_CODE); + if (biddercustom) { + Object.keys(biddercustom) + .filter(param => includes(USER_PARAMS_AUCTION, param)) + .forEach(param => beaconParams[param] = encodeURIComponent(biddercustom[param])) } + // per impression parameters + let adZoneIds = []; + let prebidBidIds = []; + let sizes = []; + let bidfloors = []; + + validBidRequests.forEach((bid, index) => { + adZoneIds.push(getBidIdParameter('adzoneid', bid.params)); + prebidBidIds.push(bid.bidId); + + let bidfloor = getFloor(bid); + bidfloors.push(bidfloor); + + // copy all custom parameters impression level parameters not supported above + let customBidParams = getBidIdParameter('custom', bid.params) || {} + if (customBidParams) { + Object.keys(customBidParams) + .filter(param => includes(USER_PARAMS_BID, param)) + .forEach(param => beaconParams[param + '.' + index] = encodeURIComponent(customBidParams[param])) + } + + if (isBannerRequest(bid)) { + sizes.push(parseSizesInput(bid.mediaTypes.banner.sizes).join('|')); + } + + if (isNativeRequest(bid)) { + sizes.push('0x0'); + } + + if (isVideoRequest(bid)) { + if (bid.params.video) { + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => beaconParams['video.' + param + '.' + index] = encodeURIComponent(bid.params.video[param])) + } + // copy video standarized params + beaconParams['video.context' + '.' + index] = deepAccess(bid, 'mediaTypes.video.context'); + sizes.push(parseSizesInput(bid.mediaTypes.video.playerSize).join('|')); + beaconParams['video.mimes' + '.' + index] = deepAccess(bid, 'mediaTypes.video.mimes').join(','); + beaconParams['video.protocols' + '.' + index] = deepAccess(bid, 'mediaTypes.video.protocols').join(','); + } + }) + + beaconParams.adzoneid = adZoneIds.join(','); + beaconParams.format = sizes.join(','); + beaconParams.prebidBidIds = prebidBidIds.join(','); + beaconParams.bidfloors = bidfloors.join(','); + + let adxcgRequestUrl = buildUrl({ + protocol: 'https', + hostname: 'hbps.adxcg.net', + pathname: '/get/adi', + search: beaconParams + }); + + logMessage(`calling adi adxcg`); return { - method: 'POST', - url: SECURE_BID_URL, - data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + contentType: 'text/plain', + method: 'GET', + url: adxcgRequestUrl, + withCredentials: true }; }, - interpretResponse: function(serverResponse, { bids }) { - if (!serverResponse.body) { - return; - } - const { seatbid, cur } = serverResponse.body; - - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; - return result; - }, []); - - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (bidResponse) { - const mediaType = deepAccess(bidResponse, 'ext.crType'); - const result = { - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - ttl: bidResponse.ttl ? bidResponse.ttl : 300, - netRevenue: bid.netRevenue === 'net', - currency: cur, - burl: bid.burl || '', - mediaType: mediaType, - width: bidResponse.w, - height: bidResponse.h, - dealId: bidResponse.dealid, - }; - - deepSetValue(result, 'meta.mediaType', mediaType); - if (isArray(bidResponse.adomain)) { - deepSetValue(result, 'meta.advertiserDomains', bidResponse.adomain); - } + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {bidRequests[]} An array of bids which were nested inside the server. + */ + interpretResponse: + function (serverResponse) { + logMessage(`interpretResponse adxcg`); + let bidsAll = []; + + if (!serverResponse || !serverResponse.body || !isArray(serverResponse.body.seatbid) || !serverResponse.body.seatbid.length) { + logWarn(BIDDER_CODE + ': empty bid response'); + return bidsAll; + } + + serverResponse.body.seatbid.forEach((bids) => { + bids.bid.forEach((serverResponseOneItem) => { + let bid = {} + // parse general fields + bid.requestId = serverResponseOneItem.impid; + bid.cpm = serverResponseOneItem.price; + bid.creativeId = parseInt(serverResponseOneItem.crid); + bid.currency = serverResponseOneItem.currency ? serverResponseOneItem.currency : 'USD'; + bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true; + bid.ttl = serverResponseOneItem.ttl ? serverResponseOneItem.ttl : 300; + bid.width = serverResponseOneItem.w; + bid.height = serverResponseOneItem.h; + bid.burl = serverResponseOneItem.burl || ''; + + if (serverResponseOneItem.dealid != null && serverResponseOneItem.dealid.trim().length > 0) { + bid.dealId = serverResponseOneItem.dealid; + } - if (isPlainObject(bidResponse.ext)) { - if (isStr(bidResponse.ext.mediaType)) { - deepSetValue(result, 'meta.mediaType', mediaType); + if (serverResponseOneItem.ext.crType === 'banner') { + bid.ad = serverResponseOneItem.adm; + } else if (serverResponseOneItem.ext.crType === 'video') { + bid.vastUrl = serverResponseOneItem.nurl; + bid.vastXml = serverResponseOneItem.adm; + bid.mediaType = 'video'; + } else if (serverResponseOneItem.ext.crType === 'native') { + bid.mediaType = 'native'; + bid.native = parseNative(JSON.parse(serverResponseOneItem.adm)); + } else { + logWarn(BIDDER_CODE + ': unknown or undefined crType'); } - if (isStr(bidResponse.ext.advertiser_id)) { - deepSetValue(result, 'meta.advertiserId', bidResponse.ext.advertiser_id); + + // prebid 4.0 meta taxonomy + if (isArray(serverResponseOneItem.adomain)) { + deepSetValue(bid, 'meta.advertiserDomains', serverResponseOneItem.adomain); } - if (isStr(bidResponse.ext.advertiser_name)) { - deepSetValue(result, 'meta.advertiserName', bidResponse.ext.advertiser_name); + if (isArray(serverResponseOneItem.cat)) { + deepSetValue(bid, 'meta.secondaryCatIds', serverResponseOneItem.cat); } - if (isStr(bidResponse.ext.agency_name)) { - deepSetValue(result, 'meta.agencyName', bidResponse.ext.agency_name); + if (isPlainObject(serverResponseOneItem.ext)) { + if (isStr(serverResponseOneItem.ext.advertiser_id)) { + deepSetValue(bid, 'meta.mediaType', serverResponseOneItem.ext.mediaType); + } + if (isStr(serverResponseOneItem.ext.advertiser_id)) { + deepSetValue(bid, 'meta.advertiserId', serverResponseOneItem.ext.advertiser_id); + } + if (isStr(serverResponseOneItem.ext.advertiser_name)) { + deepSetValue(bid, 'meta.advertiserName', serverResponseOneItem.ext.advertiser_name); + } + if (isStr(serverResponseOneItem.ext.agency_name)) { + deepSetValue(bid, 'meta.agencyName', serverResponseOneItem.ext.agency_name); + } } - } - if (mediaType === BANNER) { - result.ad = bidResponse.adm; - } else if (mediaType === NATIVE) { - result.native = parseNative(bidResponse); - result.width = 0; - result.height = 0; - } else if (mediaType === VIDEO) { - result.vastUrl = bidResponse.nurl; - result.vastXml = bidResponse.adm; - } + bidsAll.push(bid) + }) + }) + return bidsAll + }, - return result; - } - }).filter(Boolean); + onBidWon: (bid) => { + if (bid.burl) { + triggerPixel(replaceAuctionPrice(bid.burl, bid.originalCpm)); + } }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - const syncs = []; - let syncUrl = config.getConfig('adxcg.usersyncUrl'); - - let query = []; - if (syncOptions.pixelEnabled && syncUrl) { - if (gdprConsent) { - query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); - query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - if (uspConsent) { - query.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - syncs.push({ - type: 'image', - url: syncUrl + (query.length ? '?' + query.join('&') : '') - }); + onTimeout(timeoutData) { + if (timeoutData == null) { + return; } - return syncs; + + let beaconParams = { + A: timeoutData.bidder, + bid: timeoutData.bidId, + a: timeoutData.adUnitCode, + cn: timeoutData.timeout, + aud: timeoutData.auctionId, + }; + let adxcgRequestUrl = buildUrl({ + protocol: 'https', + hostname: 'hbps.adxcg.net', + pathname: '/event/timeout.gif', + search: beaconParams + }); + logWarn(BIDDER_CODE + ': onTimeout called'); + triggerPixel(adxcgRequestUrl); }, - onBidWon: (bid) => { - // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server - // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute - if (bid.nurl) { - triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm)) + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + let params = ''; + if (gdprConsent && 'gdprApplies' in gdprConsent) { + if (gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://cdn.adxcg.net/pb-sync.html' + params + }]; } } -}; +} -registerBidder(spec); +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} -function parseNative(bid) { - const { assets, link, imptrackers, jstracker } = JSON.parse(bid.adm); - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - return result; +function isBannerRequest(bid) { + return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner'); } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } +function isNativeRequest(bid) { + return bid.mediaType === 'native' || !!deepAccess(bid, 'mediaTypes.native'); +} + +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR); + } + + try { + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + bidRequest: bid + }); + return floor.floor; + } catch (e) { + logWarn(BIDDER_CODE + ': call to getFloor failed:' + e.message); + return DEFAULT_MIN_FLOOR; } } -function flatten(arr) { - return [].concat(...arr); +function parseNative(nativeResponse) { + let bidNative = {}; + bidNative = { + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers, + clickTrackers: nativeResponse.clktrackers, + javascriptTrackers: nativeResponse.jstrackers + }; + + nativeResponse.assets.forEach(asset => { + if (asset.title && asset.title.text) { + bidNative.title = asset.title.text; + } + + if (asset.img && asset.img.url) { + bidNative.image = { + url: asset.img.url, + height: asset.img.h, + width: asset.img.w + }; + } + + if (asset.icon && asset.icon.url) { + bidNative.icon = { + url: asset.icon.url, + height: asset.icon.h, + width: asset.icon.w + }; + } + + if (asset.data && asset.data.label === 'DESC' && asset.data.value) { + bidNative.body = asset.data.value; + } + + if (asset.data && asset.data.label === 'SPONSORED' && asset.data.value) { + bidNative.sponsoredBy = asset.data.value; + } + }) + return bidNative; } + +registerBidder(spec) diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 7721295572c..551d50b60e7 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,902 +1,653 @@ -// jshint esversion: 6, es3: false, node: true -import {assert} from 'chai'; +import {expect} from 'chai'; import {spec} from 'modules/adxcgBidAdapter.js'; -import {config} from 'src/config.js'; -import {createEidsArray} from 'modules/userId/eids.js'; -const utils = require('src/utils'); - -describe('Adxcg adapter', function () { - let bids = []; - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '19910113' +import {deepClone, parseUrl} from 'src/utils.js'; +import * as utils from '../../../src/utils.js'; + +describe('AdxcgAdapter', function () { + let bidBanner = { + bidder: 'adxcg', + params: { + adzoneid: '1' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [640, 360], + [1, 1] + ] } - }; - - it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); + }, + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + let bidVideo = { + bidder: 'adxcg', + params: { + adzoneid: '20', + video: { + api: [2], + maxduration: 30 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + protocols: [1, 2], + mimes: ['video/mp4'], + } + }, + adUnitCode: 'adunit-code', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + let bidNative = { + bidder: 'adxcg', + params: { + adzoneid: '2379' + }, + mediaTypes: { + native: { + image: { + sendId: false, + required: true, + sizes: [80, 80] + }, + title: { + required: true, + len: 75 + }, + body: { + required: true, + len: 200 + }, + sponsoredBy: { + required: false, + len: 20 + } + } + }, + adUnitCode: 'adunit-code', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; - bid.params = { - adzoneid: 4332, - }; - assert(spec.isBidRequestValid(bid)); + describe('isBidRequestValid', function () { + it('should return true when required params found bidNative', function () { + expect(spec.isBidRequestValid(bidNative)).to.equal(true); }); - it('should return false when required params are missing', function () { - bid.params = {}; - assert.isFalse(spec.isBidRequestValid(bid)); - - bid.params = { - mname: 'some-placement' - }; - assert.isFalse(spec.isBidRequestValid(bid)); - - bid.params = { - inv: 1234 - }; - assert.isFalse(spec.isBidRequestValid(bid)); + it('should return true when required params found bidVideo', function () { + expect(spec.isBidRequestValid(bidVideo)).to.equal(true); }); - }); - describe('buildRequests', function () { - beforeEach(function () { - config.resetConfig(); + it('should return true when required params found bidBanner', function () { + expect(spec.isBidRequestValid(bidBanner)).to.equal(true); }); - it('should send request with correct structure', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: { - adzoneid: '19910113' - } - }]; - let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); - assert.equal(request.method, 'POST'); - assert.equal(request.url, 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); - assert.deepEqual(request.options, {contentType: 'application/json'}); - assert.ok(request.data); + it('should return false when required params not found', function () { + expect(spec.isBidRequestValid({})).to.be.false; }); - describe('user privacy', function () { - it('should send GDPR Consent data to exchange if gdprApplies', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = { - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); - assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); - assert.equal(typeof request.regs.ext.gdpr, 'number'); - }); - - it('should send gdpr as number', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = { - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(typeof request.regs.ext.gdpr, 'number'); - assert.equal(request.regs.ext.gdpr, 1); - }); - - it('should send CCPA Consent data to exchange', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = {uspConsent: '1YA-', refererInfo: {referer: 'page'}}; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.ext.us_privacy, '1YA-'); - - bidderRequest = { - uspConsent: '1YA-', - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.ext.us_privacy, '1YA-'); - assert.equal(request.user.ext.consent, 'consentDataString'); - assert.equal(request.regs.ext.gdpr, 1); - }); - - it('should not send GDPR Consent data to adxcg if gdprApplies is undefined', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let bidderRequest = { - gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, 'consentDataString'); - assert.equal(request.regs.ext.gdpr, 0); - - bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: {referer: 'page'}}; - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); - it('should send default GDPR Consent data to exchange', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bidBanner); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should add test and is_debug to request, if test is set in parameters', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {test: 1} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.ok(request.is_debug); - assert.equal(request.test, 1); + it('should return true when required video params not found', function () { + const simpleVideo = JSON.parse(JSON.stringify(bidVideo)); + simpleVideo.params.adzoneid = 123; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; }); + }); - it('should have default request structure', function () { - let keys = 'site,geo,device,source,ext,imp'.split(','); - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - let data = Object.keys(request); - - assert.deepEqual(keys, data); + describe('request function http', function () { + it('creates a valid adxcg request url bidBanner', function () { + let request = spec.buildRequests([bidBanner]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = parseUrl(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20210330PB40'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('1'); + expect(query.format).to.equal('300x250|640x360|1x1'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); }); - it('should set request keys correct values', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'}, - transactionId: 'transactionId' - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.source.tid, validBidRequests[0].transactionId); - assert.equal(request.source.fd, 1); + it('creates a valid adxcg request url bidVideo', function () { + let request = spec.buildRequests([bidVideo]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = parseUrl(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + // general part + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20210330PB40'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('20'); + expect(query.format).to.equal('640x480'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); + + // video specific part + expect(query['video.maxduration.0']).to.equal('30'); + expect(query['video.mimes.0']).to.equal('video/mp4'); + expect(query['video.context.0']).to.equal('instream'); }); - it('should send info about device', function () { - config.setConfig({ - device: {w: 100, h: 100} - }); - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.device.ua, navigator.userAgent); - assert.equal(request.device.w, 100); - assert.equal(request.device.h, 100); + it('creates a valid adxcg request url bidNative', function () { + let request = spec.buildRequests([bidNative]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = parseUrl(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20210330PB40'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('2379'); + expect(query.format).to.equal('0x0'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); }); + }); - it('should send app info', function () { - config.setConfig({ - app: {id: 'appid'}, - ortb2: {app: {name: 'appname'}} + describe('gdpr compliance', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests([bidBanner], { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString' + } }); - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.app.id, 'appid'); - assert.equal(request.app.name, 'appname'); - assert.equal(request.site, undefined); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; + + expect(query.gdpr).to.equal('1'); + expect(query.gdpr_consent).to.equal('consentDataString'); }); - it('should send info about the site', function () { - config.setConfig({ - site: { - id: '123123', - publisher: { - domain: 'publisher.domain.com' - } - }, - ortb2: { - site: { - publisher: { - id: 4441, - name: 'publisher\'s name' - } - } + it('should not send GDPR Consent data if gdprApplies is false or undefined', function () { + let request = spec.buildRequests([bidBanner], { + gdprConsent: { + gdprApplies: false, + consentString: 'consentDataString' } }); - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'} - }]; - let refererInfo = {referer: 'page'}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); - - assert.deepEqual(request.site, { - domain: 'localhost', - id: '123123', - page: refererInfo.referer, - publisher: { - domain: 'publisher.domain.com', - id: 4441, - name: 'publisher\'s name' - } - }); - }); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; - it('should pass extended ids', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) - }]; - - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - assert.deepEqual(request.user.ext.eids, [ - {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} - ]); + expect(query.gdpr).to.be.undefined; + expect(query.gdpr_consent).to.be.undefined; }); + }); - it('should send currency if defined', function () { - config.setConfig({currency: {adServerCurrency: 'EUR'}}); - let validBidRequests = [{params: {}}]; - let refererInfo = {referer: 'page'}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); + describe('userid pubcid should be passed to querystring', function () { + let bidderRequests = {}; + let bid = deepClone([bidBanner]); + bid[0].userId = {pubcid: 'pubcidabcd'}; - assert.deepEqual(request.cur, ['EUR']); + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; + expect(query.pubcid).to.equal('pubcidabcd'); }); + }); - it('should pass supply chain object', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - schain: { - validation: 'strict', - config: { - ver: '1.0' - } - } - }]; + describe('userid tdid should be passed to querystring', function () { + let bid = deepClone([bidBanner]); + let bidderRequests = {}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - assert.deepEqual(request.source.ext.schain, { - validation: 'strict', - config: { - ver: '1.0' - } - }); + bid[0].userId = {tdid: 'tdidabcd'}; + + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; + expect(query.tdid).to.equal('tdidabcd'); }); + }); - describe('bids', function () { - it('should add more than one bid to the request', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }, { - bidId: 'bidId2', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.imp.length, 2); - }); - it('should add incrementing values of id', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }, { - bidId: 'bidId2', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }, { - bidId: 'bidId3', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - - for (let i = 0; i < 3; i++) { - assert.equal(imps[i].id, i + 1); - } - }); + describe('userid id5id should be passed to querystring', function () { + let bid = deepClone([bidBanner]); + let bidderRequests = {}; - it('should add adzoneid', function () { - let validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}, - {bidId: 'bidId2', params: {adzoneid: 1001}, mediaTypes: {video: {}}}, - {bidId: 'bidId3', params: {adzoneid: 1002}, mediaTypes: {video: {}}}]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - for (let i = 0; i < 3; i++) { - assert.equal(imps[i].tagid, validBidRequests[i].params.adzoneid); - } - }); + bid[0].userId = {id5id: {uid: 'id5idsample'}}; - describe('price floors', function () { - it('should not add if floors module not configured', function () { - const validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, undefined); - }); - - it('should not add if floor price not defined', function () { - const validBidRequests = [getBidWithFloor()]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'USD'); - }); - - it('should request floor price in adserver currency', function () { - config.setConfig({currency: {adServerCurrency: 'DKK'}}); - const validBidRequests = [getBidWithFloor()]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'DKK'); - }); - - it('should add correct floor values', function () { - const expectedFloors = [1, 1.3, 0.5]; - const validBidRequests = expectedFloors.map(getBidWithFloor); - let imps = getRequestImps(validBidRequests); - - expectedFloors.forEach((floor, index) => { - assert.equal(imps[index].bidfloor, floor); - assert.equal(imps[index].bidfloorcur, 'USD'); - }); - }); - - function getBidWithFloor(floor) { - return { - params: {adzoneid: 1}, - mediaTypes: {video: {}}, - getFloor: ({currency}) => { - return { - currency: currency, - floor - }; - } - }; - } - }); + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; + expect(query.id5id).to.equal('id5idsample'); + }); + }); - describe('multiple media types', function () { - it('should use all configured media types for bidding', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] - }, - video: {} - } - }, { - bidId: 'bidId1', - params: {adzoneid: 1000}, - mediaTypes: { - video: {}, - native: {} - } - }, { - bidId: 'bidId2', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140} - }, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] - }, - native: {}, - video: {} - } - }]; - let [first, second, third] = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; + describe('userid idl_env should be passed to querystring', function () { + let bid = deepClone([bidBanner]); + let bidderRequests = {}; - assert.ok(first.banner); - assert.ok(first.video); - assert.equal(first.native, undefined); + bid[0].userId = {idl_env: 'idl_envsample'}; - assert.ok(second.video); - assert.equal(second.banner, undefined); - assert.equal(second.native, undefined); + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = parseUrl(request.url); + let query = parsedRequestUrl.search; + expect(query.idl_env).to.equal('idl_envsample'); + }); + }); - assert.ok(third.native); - assert.ok(third.video); - assert.ok(third.banner); - }); - }); + describe('response handler', function () { + let BIDDER_REQUEST = { + bidder: 'adxcg', + params: { + adzoneid: '1' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [640, 360], + [1, 1] + ] + } + }, + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; - describe('banner', function () { - it('should convert sizes to openrtb format', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] + let BANNER_RESPONSE = { + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + adm: '', + w: 300, + h: 250, + adomain: ['adomain.com'], + cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], + ext: { + crType: 'banner', + advertiser_id: '777', + advertiser_name: 'advertiser', + agency_name: 'agency' } } - }]; - let {banner} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; - assert.deepEqual(banner, { - format: [{w: 100, h: 100}, {w: 200, h: 300}] - }); - }); - }); + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} + }; - describe('video', function () { - it('should pass video mediatype config', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'] + let BANNER_RESPONSE_WITHDEALID = { + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + dealid: '7722', + adm: '', + w: 300, + h: 250, + adomain: ['adomain.com'], + ext: { + crType: 'banner' } } - }]; - let {video} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; - assert.deepEqual(video, { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'] - }); - }); - }); + ] + }], + cur: 'USD' + } + }; - describe('native', function () { - describe('assets', function () { - it('should set correct asset id', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - }]; - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - - assert.equal(assets[0].id, 0); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 4); - }); - it('should add required key if it is necessary', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140}, - sponsoredBy: {required: true, len: 140} - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - - assert.equal(assets[0].required, 1); - assert.ok(!assets[1].required); - assert.ok(!assets[2].required); - assert.equal(assets[3].required, 1); - }); - - it('should map img and data assets', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: true, sizes: [150, 50]}, - icon: {required: false, sizes: [50, 50]}, - body: {required: false, len: 140}, - sponsoredBy: {required: true}, - cta: {required: false}, - clickUrl: {required: false} - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].title); - assert.equal(assets[0].title.len, 140); - assert.deepEqual(assets[1].img, {type: 3, w: 150, h: 50}); - assert.deepEqual(assets[2].img, {type: 1, w: 50, h: 50}); - assert.deepEqual(assets[3].data, {type: 2, len: 140}); - assert.deepEqual(assets[4].data, {type: 1}); - assert.deepEqual(assets[5].data, {type: 12}); - assert.ok(!assets[6]); - }); - - describe('icon/image sizing', function () { - it('should flatten sizes and utilise first pair', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - sizes: [[200, 300], [100, 200]] - }, - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.w, 200); - assert.equal(assets[0].img.h, 300); - }); - }); - - it('should utilise aspect_ratios', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [{ - min_width: 100, - ratio_height: 3, - ratio_width: 1 - }] - }, - icon: { - aspect_ratios: [{ - min_width: 10, - ratio_height: 5, - ratio_width: 2 - }] - } - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 100); - assert.equal(assets[0].img.hmin, 300); - - assert.ok(assets[1].img); - assert.equal(assets[1].img.wmin, 10); - assert.equal(assets[1].img.hmin, 25); - }); - - it('should not throw error if aspect_ratios config is not defined', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [] - }, - icon: { - aspect_ratios: [] - } - } - }]; - - assert.doesNotThrow(() => spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}})); - }); - }); - - it('should expect any dimensions if min_width not passed', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [{ - ratio_height: 3, - ratio_width: 1 - }] + let VIDEO_RESPONSE = { + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + nurl: 'vastContentUrl', + adomain: ['adomain.com'], + w: 640, + h: 360, + ext: { + crType: 'video' } } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 0); - assert.equal(assets[0].img.hmin, 0); - assert.ok(!assets[1]); - }); - }); - }); + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} + }; - function getRequestImps(validBidRequests) { - return JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; + let NATIVE_RESPONSEob = { + assets: [ + { + id: 1, + required: 0, + title: { + text: 'titleContent' + } + }, + { + id: 2, + required: 0, + img: { + url: 'imageContent', + w: 600, + h: 600 + } + }, + { + id: 3, + required: 0, + data: { + label: 'DESC', + value: 'descriptionContent' + } + }, + { + id: 0, + required: 0, + data: { + label: 'SPONSORED', + value: 'sponsoredByContent' + } + }, + { + id: 5, + required: 0, + icon: { + url: 'iconContent', + w: 400, + h: 400 + } + } + ], + link: { + url: 'linkContent' + }, + imptrackers: ['impressionTracker1', 'impressionTracker2'] } - }); - - describe('interpretResponse', function () { - it('should return if no body in response', function () { - let serverResponse = {}; - let bidRequest = {}; - assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); - }); - it('should return more than one bids', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{ - impid: '1', - native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, title: {text: 'Asset title text'}}]} - }] - }, { - bid: [{ - impid: '2', - native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, data: {value: 'Asset title text'}}]} - }] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - }, - { - bidId: 'bidId2', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} + let NATIVE_RESPONSE = { + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + w: 0, + h: 0, + adm: JSON.stringify(NATIVE_RESPONSEob), + adomain: ['adomain.com'], + ext: { + crType: 'native' + } } - } - ] - }; + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} + }; - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(spec.interpretResponse(serverResponse, bidRequest).length, 2); + it('handles regular responses', function () { + expect(BANNER_RESPONSE).to.exist; + expect(BANNER_RESPONSE.body).to.exist; + expect(BANNER_RESPONSE.body.id).to.exist; + expect(BANNER_RESPONSE.body.seatbid[0]).to.exist; + let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.be.within(0.45, 0.46); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + expect(result[0].meta.advertiserDomains[0]).to.equal('adomain.com'); + expect(result[0].meta.advertiserId).to.be.eql('777'); + expect(result[0].meta.advertiserName).to.be.eql('advertiser'); + expect(result[0].meta.agencyName).to.be.eql('agency'); + expect(result[0].meta.advertiserDomains).to.be.eql(['adomain.com']); + expect(result[0].meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); }); - it('should set correct values to bid', function () { - let nativeExample1 = { - assets: [], - link: {url: 'link'}, - imptrackers: ['imptrackers url1', 'imptrackers url2'] - } + it('handles regular responses with dealid', function () { + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID); - let serverResponse = { - body: { - id: null, - bidid: null, - seatbid: [{ - bid: [ - { - impid: '1', - price: 93.1231, - crid: '12312312', - adm: JSON.stringify(nativeExample1), - dealid: 'deal-id', - adomain: ['demo.com'], - ext: { - crType: 'native', - advertiser_id: 'adv1', - advertiser_name: 'advname', - agency_name: 'agname', - mediaType: 'native' - } - } - ] - }], - cur: 'EUR' - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - } - ] - }; + expect(result).to.have.lengthOf(1); - const bids = spec.interpretResponse(serverResponse, bidRequest); - const bid = serverResponse.body.seatbid[0].bid[0]; - assert.deepEqual(bids[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(bids[0].cpm, bid.price); - assert.deepEqual(bids[0].creativeId, bid.crid); - assert.deepEqual(bids[0].ttl, 300); - assert.deepEqual(bids[0].netRevenue, false); - assert.deepEqual(bids[0].currency, serverResponse.body.cur); - assert.deepEqual(bids[0].mediaType, 'native'); - assert.deepEqual(bids[0].meta.mediaType, 'native'); - assert.deepEqual(bids[0].meta.advertiserDomains, ['demo.com']); - - assert.deepEqual(bids[0].meta.advertiserName, 'advname'); - assert.deepEqual(bids[0].meta.agencyName, 'agname'); - - assert.deepEqual(bids[0].dealId, 'deal-id'); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + // expect(result[0].cpm).to.equal(0.45); + expect(result[0].cpm).to.be.within(0.45, 0.46); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); - it('should return empty when there is no bids in response', function () { - const serverResponse = { - body: { - id: null, - bidid: null, - seatbid: [{bid: []}], - cur: 'EUR' - } - }; - let bidRequest = { - data: {}, - bids: [{bidId: 'bidId1'}] - }; - const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.ok(!result); + it('handles video responses', function () { + let result = spec.interpretResponse(VIDEO_RESPONSE); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(360); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].vastUrl).to.equal('vastContentUrl'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); - describe('banner', function () { - it('should set ad content on response', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{impid: '1', adm: '', ext: {crType: 'banner'}}] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000} - } - ] - }; + it('handles native responses', function () { + let result = spec.interpretResponse(NATIVE_RESPONSE); - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(bids.length, 1); - assert.equal(bids[0].ad, ''); - assert.equal(bids[0].mediaType, 'banner'); - assert.equal(bids[0].meta.mediaType, 'banner'); - }); + expect(result[0].width).to.equal(0); + expect(result[0].height).to.equal(0); + + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + + expect(result[0].mediaType).to.equal('native'); + + expect(result[0].native.clickUrl).to.equal('linkContent'); + expect(result[0].native.impressionTrackers).to.deep.equal([ + 'impressionTracker1', + 'impressionTracker2' + ]); + expect(result[0].native.title).to.equal('titleContent'); + + expect(result[0].native.image.url).to.equal('imageContent'); + expect(result[0].native.image.height).to.equal(600); + expect(result[0].native.image.width).to.equal(600); + + expect(result[0].native.icon.url).to.equal('iconContent'); + expect(result[0].native.icon.height).to.equal(400); + expect(result[0].native.icon.width).to.equal(400); + + expect(result[0].native.body).to.equal('descriptionContent'); + expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); }); - describe('video', function () { - it('should set vastXml on response', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{impid: '1', adm: '', ext: {crType: 'video'}}] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000} - } - ] - }; + it('handles nobid responses', function () { + let response = []; + let bidderRequest = BIDDER_REQUEST; - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(bids.length, 1); - assert.equal(bids[0].vastXml, ''); - assert.equal(bids[0].mediaType, 'video'); - assert.equal(bids[0].meta.mediaType, 'video'); - }); + let result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); }); }); describe('getUserSyncs', function () { - const usersyncUrl = 'https://usersync-url.com'; - beforeEach(() => { - config.setConfig( - { - adxcg: { - usersyncUrl: usersyncUrl, - } - } - ) - }) - after(() => { - config.resetConfig() - }) - - it('should return user sync if pixel enabled with adxcg config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) - }) - - it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) - expect(ret).to.be.an('array').that.is.empty - }) - - it('should not return user sync if url is not set', function () { - config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.be.an('array').that.is.empty - }) - - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` - }]); - }); - - it('should pass US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` - }]); - }); + let syncoptionsIframe = { + iframeEnabled: 'true' + }; - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` - }]); + it('should return iframe sync option', function () { + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal( + 'https://cdn.adxcg.net/pb-sync.html' + ); }); }); - describe('onBidWon', function() { - beforeEach(function() { + describe('on bidWon', function () { + beforeEach(function () { sinon.stub(utils, 'triggerPixel'); }); - afterEach(function() { + afterEach(function () { utils.triggerPixel.restore(); }); - - it('Should trigger pixel if bid nurl', function() { + it('should replace burl for banner', function () { + const burl = 'burl=${' + 'AUCTION_PRICE}'; const bid = { - nurl: 'http://example.com/win/${AUCTION_PRICE}', - cpm: 2.1, - originalCpm: 1.1, - } + 'bidderCode': 'adxcg', + 'width': 0, + 'height': 0, + 'statusMessage': 'Bid available', + 'adId': '3d0b6ff1dda89', + 'requestId': '2a423489e058a1', + 'mediaType': 'banner', + 'source': 'client', + 'ad': burl, + 'cpm': 0.66, + 'creativeId': '353538_591471', + 'currency': 'USD', + 'dealId': '', + 'netRevenue': true, + 'ttl': 300, + // 'nurl': nurl, + 'burl': burl, + 'isBurl': true, + 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', + 'responseTimestamp': 1556867386065, + 'requestTimestamp': 1556867385916, + 'bidder': 'adxcg', + 'adUnitCode': 'div-gpt-ad-1555415275793-0', + 'timeToRespond': 149, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.66', + 'pbAg': '0.65', + 'pbDg': '0.66', + 'pbCg': '', + 'size': '0x0', + 'adserverTargeting': { + 'hb_bidder': 'mgid', + 'hb_adid': '3d0b6ff1dda89', + 'hb_pb': '0.66', + 'hb_size': '0x0', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_banner_title': 'TITLE', + 'hb_banner_image': 'hb_banner_image:3d0b6ff1dda89', + 'hb_banner_icon': 'IconURL', + 'hb_banner_linkurl': 'hb_banner_linkurl:3d0b6ff1dda89' + }, + 'status': 'targetingSet', + 'params': [{'adzoneid': '20'}] + }; spec.onBidWon(bid); - expect(utils.triggerPixel.callCount).to.equal(1) - }) - }) + expect(bid.burl).to.deep.equal(burl); + }); + }); });