diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 18016eea530..c696dabc64a 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,69 +1,58 @@ import { - _each, - deepAccess, - deepSetValue, - getBidIdParameter, - getBidRequest, - getUniqueIdentifierStr, - isArray, - isEmpty, - isFn, - isInteger, - isNumber, - isPlainObject, - isStr, - logError, - logWarn, mergeDeep + cleanObj, deepAccess, deepClone, deepSetValue, getBidIdParameter, getBidRequest, getDNT, + getUniqueIdentifierStr, isFn, isPlainObject, logWarn, mergeDeep, parseUrl } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {createEidsArray} from './userId/eids.js'; -import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'improvedigital'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; +const REQUEST_URL = 'https://ad.360yield.com/pb'; +const CREATIVE_TTL = 300; + +const VIDEO_PARAMS = { + DEFAULT_MIMES: ['video/mp4'], + SUPPORTED_PROPERTIES: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', + 'api', 'companiontype', 'ext'], + PLACEMENT_TYPE: { + INSTREAM: 1, + OUTSTREAM: 3, + } +}; -const ID_RAZR = { - RENDERER_URL: 'https://razr.improvedigital.com/renderer.js', - addBidData({bid, bidRequest}) { - if (this.isValidBid(bid)) { - bid.renderer = Renderer.install({ - url: this.RENDERER_URL, - config: {bidRequest} - }); - bid.renderer.setRender(this.render); - } +const NATIVE_DATA = { + VERSION: '1.2', + ASSET_TYPES: { + TITLE: 'title', + IMG: 'img', + DATA: 'data', }, - - isValidBid(bid) { - return bid && /razr:\\?\/\\?\//.test(bid.ad); + ASSETS: { + title: {id: 0, name: 'title', assetType: 'title', default: {len: 140}}, + sponsoredBy: {id: 1, name: 'sponsoredBy', assetType: 'data', type: 1}, + icon: {id: 2, name: 'icon', assetType: 'img', type: 2}, + body: {id: 3, name: 'body', assetType: 'data', type: 2}, + image: {id: 4, name: 'image', assetType: 'img', type: 3}, + rating: {id: 5, name: 'rating', assetType: 'data', type: 3}, + likes: {id: 6, name: 'likes', assetType: 'data', type: 4}, + downloads: {id: 7, name: 'downloads', assetType: 'data', type: 5}, + price: {id: 8, name: 'price', assetType: 'data', type: 6}, + salePrice: {id: 9, name: 'salePrice', assetType: 'data', type: 7}, + phone: {id: 10, name: 'phone', assetType: 'data', type: 8}, + address: {id: 11, name: 'address', assetType: 'data', type: 9}, + body2: {id: 12, name: 'body2', assetType: 'data', type: 10}, + displayUrl: {id: 13, name: 'displayUrl', assetType: 'data', type: 11}, + cta: {id: 14, name: 'cta', assetType: 'data', type: 12}, }, - - render(bid) { - const {bidRequest} = bid.renderer.getConfig(); - - const payload = { - type: 'prebid', - bidRequest, - bid, - config: mergeDeep( - {}, - config.getConfig('improvedigital.rendererConfig'), - deepAccess(bidRequest, 'params.rendererConfig') - ) - }; - - const razr = window.razr = window.razr || {}; - razr.queue = razr.queue || []; - razr.queue.push(payload); + getAssetById(id) { + return Object.values(this.ASSETS).find(asset => id === asset.id); } }; export const spec = { - version: '7.7.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -75,7 +64,7 @@ export const spec = { * @param {object} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { return !!(bid && bid.params && (bid.params.placementId || (bid.params.placementKey && bid.params.publisherId))); }, @@ -83,179 +72,130 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (bidRequests, bidderRequest) { - let normalizedBids = bidRequests.map((bidRequest) => { - return getNormalizedBidRequest(bidRequest); - }); - - let idClient = new ImproveDigitalAdServerJSClient('hb'); - let requestParameters = { - singleRequestMode: (config.getConfig('improvedigital.singleRequest') === true), - returnObjType: idClient.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT, - libVersion: this.version - }; - - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') - if (gdprConsent) { - // GDPR Consent String - if (gdprConsent.consentString) { - requestParameters.gdpr = gdprConsent.consentString; - } - - // Additional Consent String - const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); - if (additionalConsent && additionalConsent.indexOf('~') !== -1) { - // Google Ad Tech Provider IDs - const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); - deepSetValue( - requestParameters, - 'user.ext.consented_providers_settings.consented_providers', - atpIds.split('.').map(id => parseInt(id, 10)) - ); + buildRequests(bidRequests, bidderRequest) { + const request = { + id: getUniqueIdentifierStr(), + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + ext: { + improvedigital: { + sdk: { + name: 'pbjs', + version: '$prebid.version$', + } + } } - } + }; - if (bidderRequest && bidderRequest.uspConsent) { - requestParameters.usPrivacy = bidderRequest.uspConsent; + // Device + request.device = (typeof config.getConfig('device') === 'object') ? config.getConfig('device') : {}; + request.device.w = request.device.w || window.innerWidth; + request.device.h = request.device.h || window.innerHeight; + if (getDNT()) { + request.device.dnt = 1; } - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - requestParameters.referrer = bidderRequest.refererInfo.referer; + // Coppa + const coppa = config.getConfig('coppa'); + if (typeof coppa === 'boolean') { + deepSetValue(request, 'regs.coppa', ID_UTIL.toBit(coppa)); } - // Adding first party data - const site = config.getConfig('ortb2.site'); - if (site) { - const pageCategory = site.pagecat || site.cat; - if (pageCategory && isArray(pageCategory)) { - requestParameters.pagecat = pageCategory.filter((category) => { - return category && isStr(category) - }); + if (bidderRequest) { + // GDPR + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + deepSetValue(request, 'regs.ext.gdpr', ID_UTIL.toBit(gdprConsent.gdprApplies)); + } + deepSetValue(request, 'user.ext.consent', gdprConsent.consentString); + + // Additional Consent String + const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); + if (additionalConsent && additionalConsent.indexOf('~') !== -1) { + // Google Ad Tech Provider IDs + const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); + deepSetValue( + request, + 'user.ext.consented_providers_settings.consented_providers', + atpIds.split('.').map(id => parseInt(id, 10)) + ); + } } - const genre = deepAccess(site, 'content.genre'); - if (genre && isStr(genre)) { - requestParameters.genre = genre; + + // Timeout + deepSetValue(request, 'tmax', bidderRequest.timeout); + // US Privacy + if (typeof bidderRequest.uspConsent !== typeof undefined) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } } - // End of adding first party data - requestParameters.schain = bidRequests[0].schain; - requestParameters.coppa = config.getConfig('coppa') === true; + ID_REQUEST.buildSiteOrApp(request, bidderRequest); - if (bidRequests[0].userId) { - const eids = createEidsArray(bidRequests[0].userId); - if (eids.length) { - deepSetValue(requestParameters, 'user.ext.eids', eids); - } - } + const bidRequest0 = bidRequests[0]; - let requestObj = idClient.createRequest( - normalizedBids, // requestObject - requestParameters - ); + deepSetValue(request, 'source.ext.schain', bidRequest0.schain); + deepSetValue(request, 'source.tid', bidRequest0.transactionId); - if (requestObj.errors && requestObj.errors.length > 0) { - logError('ID WARNING 0x01'); + if (bidRequest0.userId) { + const eids = createEidsArray(bidRequest0.userId); + deepSetValue(request, 'user.ext.eids', eids.length ? eids : undefined); } - requestObj.requests.forEach(request => request.bidderRequest = bidderRequest); - return requestObj.requests; + + return ID_REQUEST.buildServerRequests(request, bidRequests, bidderRequest); }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. + * @param bidderRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, {bidderRequest}) { + interpretResponse(serverResponse, { bidderRequest }) { + if (!Array.isArray(deepAccess(serverResponse, 'body.seatbid'))) { + return []; + } + const bids = []; - _each(serverResponse.body.bid, function (bidObject) { - if (!bidObject.price || bidObject.price === null || - bidObject.hasOwnProperty('errorCode') || - (!bidObject.adm && !bidObject.native)) { - return; - } - const bidRequest = getBidRequest(bidObject.id, [bidderRequest]); - const bid = {}; - - if (bidObject.native) { - // Native - bid.native = getNormalizedNativeAd(bidObject.native); - // Expose raw oRTB response to the client to allow parsing assets not directly supported by Prebid - bid.ortbNative = bidObject.native; - if (bidObject.nurl) { - bid.native.impressionTrackers.unshift(bidObject.nurl); - } - bid.mediaType = NATIVE; - } else if (bidObject.ad_type && bidObject.ad_type === 'video') { - bid.vastXml = bidObject.adm; - bid.mediaType = VIDEO; - if (isOutstreamVideo(bidRequest)) { - bid.adResponse = { - content: bid.vastXml, - height: bidObject.h, - width: bidObject.w - }; - bid.renderer = createRenderer(bidRequest); - } - } else { - // Banner - let nurl = ''; - if (bidObject.nurl && bidObject.nurl.length > 0) { - nurl = ``; - } - bid.ad = `${nurl}`; - bid.mediaType = BANNER; - } - // Common properties - bid.cpm = parseFloat(bidObject.price); - bid.creativeId = bidObject.crid; - bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; - - // Deal ID. Composite ads can have multiple line items and the ID of the first - // dealID line item will be used. - if (isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') { - bid.dealId = bidObject.lid; - } else if (Array.isArray(bidObject.lid) && - Array.isArray(bidObject.buying_type) && - bidObject.lid.length === bidObject.buying_type.length) { - let isDeal = false; - bidObject.buying_type.forEach((bt, i) => { - if (isDeal) return; - if (bt && bt !== 'rtb') { - isDeal = true; - bid.dealId = bidObject.lid[i]; - } - }); - } + serverResponse.body.seatbid.forEach(seatbid => { + if (!Array.isArray(seatbid.bid)) return; - bid.height = bidObject.h; - bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; - bid.requestId = bidObject.id; - bid.ttl = 300; - bid.width = bidObject.w; + seatbid.bid.forEach(bidObject => { + if (!bidObject.adm || !bidObject.price || bidObject.hasOwnProperty('errorCode')) { + return; + } + const bidRequest = getBidRequest(bidObject.impid, [bidderRequest]); + const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`); + + const bid = { + requestId: bidObject.impid, + cpm: bidObject.price, + creativeId: bidObject.crid, + currency: serverResponse.body.cur.toUpperCase() || 'USD', + dealId: (typeof idExt.buying_type === 'string' && idExt.buying_type !== 'rtb') ? idExt.line_item_id : undefined, + meta: { + advertiserDomains: bidObject.adomain ? bidObject.adomain : [] + }, + netRevenue: idExt.is_net || false, + ttl: CREATIVE_TTL + } - if (!bid.width || !bid.height) { - bid.width = 1; - bid.height = 1; - } + ID_RESPONSE.buildAd(bid, bidRequest, bidObject); - if (bidObject.adomain) { - bid.meta = { - advertiserDomains: bidObject.adomain - }; - } + ID_RAZR.addBidData({ + bidRequest, + bid + }); - ID_RAZR.addBidData({ - bidRequest, - bid + bids.push(bid); }); - - bids.push(bid); }); + return bids; }, @@ -266,17 +206,14 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses) { if (syncOptions.pixelEnabled) { const syncs = []; serverResponses.forEach(response => { - response.body.bid.forEach(bidObject => { - if (isArray(bidObject.sync)) { - bidObject.sync.forEach(syncElement => { - if (syncs.indexOf(syncElement) === -1) { - syncs.push(syncElement); - } - }); + const syncArr = deepAccess(response, `body.ext.${BIDDER_CODE}.sync`, []); + syncArr.forEach(syncElement => { + if (syncs.indexOf(syncElement) === -1) { + syncs.push(syncElement); } }); }); @@ -286,529 +223,374 @@ export const spec = { } }; -function isInstreamVideo(bid) { - const mediaTypes = Object.keys(deepAccess(bid, 'mediaTypes', {})); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return bid.mediaType === 'video' || (mediaTypes.length === 1 && videoMediaType && context !== 'outstream'); -} - -function isOutstreamVideo(bid) { - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return videoMediaType && context === 'outstream'; -} - -function getVideoTargetingParams(bid) { - const result = {}; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} +registerBidder(spec); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - const floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -function outstreamRender(bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} - -function createRenderer(bidRequest) { - const renderer = Renderer.install({ - id: bidRequest.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: deepAccess(bidRequest, 'renderer.options'), - adUnitCode: bidRequest.adUnitCode - }); - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - return renderer; -} - -function getNormalizedBidRequest(bid) { - let adUnitId = getBidIdParameter('adUnitCode', bid) || null; - let placementId = getBidIdParameter('placementId', bid.params) || null; - let publisherId = null; - let placementKey = null; - - if (placementId === null) { - publisherId = getBidIdParameter('publisherId', bid.params) || null; - placementKey = getBidIdParameter('placementKey', bid.params) || null; - } - const keyValues = getBidIdParameter('keyValues', bid.params) || null; - const singleSizeFilter = getBidIdParameter('size', bid.params) || null; - const bidId = getBidIdParameter('bidId', bid); - const transactionId = getBidIdParameter('transactionId', bid); - const currency = config.getConfig('currency.adServerCurrency'); - - let normalizedBidRequest = {}; - if (isInstreamVideo(bid)) { - normalizedBidRequest.adTypes = [ VIDEO ]; - } - if (isInstreamVideo(bid) || isOutstreamVideo(bid)) { - normalizedBidRequest.video = getVideoTargetingParams(bid); - } - if (placementId) { - normalizedBidRequest.placementId = placementId; - } else { - if (publisherId) { - normalizedBidRequest.publisherId = publisherId; - } - if (placementKey) { - normalizedBidRequest.placementKey = placementKey; +const ID_REQUEST = { + buildServerRequests(requestObject, bidRequests, bidderRequest) { + const requests = []; + if (config.getConfig('improvedigital.singleRequest') === true) { + requestObject.imp = bidRequests.map((bidRequest) => this.buildImp(bidRequest)); + requests[0] = this.formatRequest(requestObject, bidderRequest); + } else { + bidRequests.map((bidRequest) => { + const request = deepClone(requestObject); + request.id = bidRequest.bidId || getUniqueIdentifierStr(); + request.imp = [this.buildImp(bidRequest)]; + deepSetValue(request, 'source.tid', bidRequest.transactionId); + requests.push(this.formatRequest(request, bidderRequest)); + }); } - } - if (keyValues) { - normalizedBidRequest.keyValues = keyValues; - } + return requests; + }, - if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && !isOutstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { - normalizedBidRequest.format = bid.sizes; - } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) { - normalizedBidRequest.size = {}; - normalizedBidRequest.size.h = singleSizeFilter.h; - normalizedBidRequest.size.w = singleSizeFilter.w; - } + formatRequest(request, bidderRequest) { + return { + method: 'POST', + url: REQUEST_URL, + data: JSON.stringify(request), + bidderRequest + } + }, - if (bidId) { - normalizedBidRequest.id = bidId; - } - if (adUnitId) { - normalizedBidRequest.adUnitId = adUnitId; - } - if (transactionId) { - normalizedBidRequest.transactionId = transactionId; - } - if (currency) { - normalizedBidRequest.currency = currency; - } - // Floor - let bidFloor = getBidFloor(bid); - let bidFloorCur = null; - if (!bidFloor) { - bidFloor = getBidIdParameter('bidFloor', bid.params); - bidFloorCur = getBidIdParameter('bidFloorCur', bid.params); - } - if (bidFloor) { - normalizedBidRequest.bidFloor = bidFloor; - normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD'; - } - return normalizedBidRequest; -} + buildImp(bidRequest) { + const imp = { + id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(), + secure: ID_UTIL.toBit(window.location.protocol === 'https:'), + }; -function getNormalizedNativeAd(rawNative) { - const native = {}; - if (!rawNative || !isArray(rawNative.assets)) { - return null; - } - // Assets - rawNative.assets.forEach(asset => { - if (asset.title) { - native.title = asset.title.text; - } else if (asset.data) { - switch (asset.data.type) { - case 1: - native.sponsoredBy = asset.data.value; - break; - case 2: - native.body = asset.data.value; - break; - case 3: - native.rating = asset.data.value; - break; - case 4: - native.likes = asset.data.value; - break; - case 5: - native.downloads = asset.data.value; - break; - case 6: - native.price = asset.data.value; - break; - case 7: - native.salePrice = asset.data.value; - break; - case 8: - native.phone = asset.data.value; - break; - case 9: - native.address = asset.data.value; - break; - case 10: - native.body2 = asset.data.value; - break; - case 11: - native.displayUrl = asset.data.value; - break; - case 12: - native.cta = asset.data.value; - break; - } - } else if (asset.img) { - switch (asset.img.type) { - case 2: - native.icon = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - case 3: - native.image = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - } + // Floor + const bidFloor = this.getBidFloor(bidRequest) || getBidIdParameter('bidFloor', bidRequest.params); + if (bidFloor) { + const bidFloorCur = getBidIdParameter('bidFloorCur', bidRequest.params) || 'USD'; + deepSetValue(imp, 'bidfloor', bidFloor); + deepSetValue(imp, 'bidfloorcur', bidFloorCur ? bidFloorCur.toUpperCase() : undefined); } - }); - // Trackers - if (rawNative.eventtrackers) { - native.impressionTrackers = []; - rawNative.eventtrackers.forEach(tracker => { - // Only handle impression event. Viewability events are not supported yet. - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - native.impressionTrackers.push(tracker.url); - break; - case 2: // js - // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. - native.javascriptTrackers = ``; - break; - } - }); - } else { - native.impressionTrackers = rawNative.imptrackers || []; - native.javascriptTrackers = rawNative.jstracker; - } - if (rawNative.link) { - native.clickUrl = rawNative.link.url; - native.clickTrackers = rawNative.link.clicktrackers; - } - if (rawNative.privacy) { - native.privacyLink = rawNative.privacy; - } - return native; -} -registerBidder(spec); -export function ImproveDigitalAdServerJSClient(endPoint) { - this.CONSTANTS = { - AD_SERVER_BASE_URL: 'ice.360yield.com', - END_POINT: endPoint || 'hb', - AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.4.0', - MAX_URL_LENGTH: 2083, - ERROR_CODES: { - MISSING_PLACEMENT_PARAMS: 2, - LIB_VERSION_MISSING: 3 - }, - RETURN_OBJ_TYPE: { - DEFAULT: 0, - URL_PARAMS_SPLIT: 1 + const placementId = getBidIdParameter('placementId', bidRequest.params); + if (placementId) { + deepSetValue(imp, 'ext.bidder.placementId', placementId); + } else { + deepSetValue(imp, 'ext.bidder.publisherId', getBidIdParameter('publisherId', bidRequest.params)); + deepSetValue(imp, 'ext.bidder.placementKey', getBidIdParameter('placementKey', bidRequest.params)); } - }; - this.getErrorReturn = function(errorCode) { - return { - idMappings: {}, - requests: {}, - 'errorCode': errorCode - }; - }; + deepSetValue(imp, 'ext.bidder.keyValues', getBidIdParameter('keyValues', bidRequest.params) || undefined); - this.createRequest = function(requestObject, requestParameters, extraRequestParameters) { - if (!requestParameters.libVersion) { - return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING); - } + // Adding GPID + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot'); - requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT; - requestParameters.adServerBaseUrl = 'https://' + (requestParameters.adServerBaseUrl || this.CONSTANTS.AD_SERVER_BASE_URL); + deepSetValue(imp, 'ext.gpid', gpid); - let impressionObjects = []; - let impressionObject; - if (isArray(requestObject)) { - for (let counter = 0; counter < requestObject.length; counter++) { - impressionObject = this.createImpressionObject(requestObject[counter]); - impressionObjects.push(impressionObject); - } - } else { - impressionObject = this.createImpressionObject(requestObject); - impressionObjects.push(impressionObject); + // Adding Interstitial Signal + if (deepAccess(bidRequest, 'ortb2Imp.instl')) { + imp.instl = 1; } - let returnIdMappings = true; - if (requestParameters.returnObjType === this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT) { - returnIdMappings = false; + const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + if (videoParams) { + imp.video = this.buildVideoRequest(bidRequest); + deepSetValue(imp, 'ext.is_rewarded_inventory', (videoParams.rewarded === 1 || deepAccess(videoParams, 'ext.rewarded') === 1) || undefined); } - let returnObject = {}; - returnObject.requests = []; - if (returnIdMappings) { - returnObject.idMappings = []; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = this.buildBannerRequest(bidRequest); } - let errors = null; - let baseUrl = `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + if (deepAccess(bidRequest, 'mediaTypes.native')) { + imp.native = this.buildNativeRequest(bidRequest); + } - let bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - for (let counter = 0; counter < impressionObjects.length; counter++) { - impressionObject = impressionObjects[counter]; - - if (impressionObject.errorCode) { - errors = errors || []; - errors.push({ - errorCode: impressionObject.errorCode, - adUnitId: impressionObject.adUnitId - }); - } else { - if (returnIdMappings) { - returnObject.idMappings.push({ - adUnitId: impressionObject.adUnitId, - id: impressionObject.impressionObject.id - }); - } - bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; - bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); - - let writeLongRequest = false; - const outputUri = baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)); - if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) { - writeLongRequest = true; - if (bidRequestObject.bid_request.imp.length > 1) { - // Pop the current request and process it again in the next iteration - bidRequestObject.bid_request.imp.pop(); - if (returnIdMappings) { - returnObject.idMappings.pop(); - } - counter--; - } - } + return imp; + }, - if (writeLongRequest || - !requestParameters.singleRequestMode || - counter === impressionObjects.length - 1) { - returnObject.requests.push(this.formatRequest(requestParameters, bidRequestObject)); - bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - } - } + buildVideoRequest(bidRequest) { + const videoParams = deepClone(bidRequest.mediaTypes.video); + const videoImproveParams = deepClone(deepAccess(bidRequest, 'params.video', {})); + const video = {...videoParams, ...videoImproveParams}; + + if (Array.isArray(video.playerSize)) { + // Player size can be defined as [w, h] or [[w, h]] + const size = Array.isArray(video.playerSize[0]) ? video.playerSize[0] : video.playerSize; + video.w = size[0]; + video.h = size[1]; } + video.placement = this.isOutstreamVideo(bidRequest) ? VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM : VIDEO_PARAMS.PLACEMENT_TYPE.INSTREAM; - if (errors) { - returnObject.errors = errors; + // Mimes is required + if (!video.mimes) { + video.mimes = VIDEO_PARAMS.DEFAULT_MIMES; } - return returnObject; - }; + // skip must be 0 or 1 + if (video.skip !== 1) { + delete video.skipmin; + delete video.skipafter; + if (video.skip !== 0) { + logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); + delete video.skip; + } + } - this.formatRequest = function(requestParameters, bidRequestObject) { - switch (requestParameters.returnObjType) { - case this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT: - return { - method: 'GET', - url: `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}`, - data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${encodeURIComponent(JSON.stringify(bidRequestObject))}` + Object.keys(video).forEach(prop => { + if (VIDEO_PARAMS.SUPPORTED_PROPERTIES.indexOf(prop) === -1) delete video[prop]; + }); + return video; + }, + + buildBannerRequest(bidRequest) { + // Set the desired creative sizes + // Input Format: array of pairs, i.e. [[300, 250], [250, 250]] + // Unless improvedigital.usePrebidSizes == true, no sizes are sent to the server + // and the sizes defined in the server for the placement will be used + const banner = {}; + if (config.getConfig('improvedigital.usePrebidSizes') === true && bidRequest.sizes) { + // Convert sizes from [x, y] to { w: x, h: y} + banner.format = bidRequest.sizes.map(sizePair => ({w: sizePair[0], h: sizePair[1]})); + } + return banner; + }, + + buildNativeRequest(bidRequest) { + const nativeParams = bidRequest.mediaTypes.native; + const request = { + assets: [], + } + for (let i of Object.keys(nativeParams)) { + const assetOrtbParams = NATIVE_DATA.ASSETS[i]; + if (assetOrtbParams) { + const assetParams = nativeParams[i]; + const asset = { + id: assetOrtbParams.id, + required: ID_UTIL.toBit(assetParams.required), }; - default: - const baseUrl = `${requestParameters.adServerBaseUrl}/` + - `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; - return { - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + switch (assetOrtbParams.assetType) { + case NATIVE_DATA.ASSET_TYPES.TITLE: + asset.title = {len: assetParams.len || assetOrtbParams.default.len}; + break; + case NATIVE_DATA.ASSET_TYPES.DATA: + asset.data = cleanObj({type: assetOrtbParams.type, len: assetParams.len}) + break; + case NATIVE_DATA.ASSET_TYPES.IMG: + asset.img = cleanObj({ + type: assetOrtbParams.type, + w: deepAccess(assetParams, 'sizes.0'), + h: deepAccess(assetParams, 'sizes.1'), + wmin: deepAccess(assetParams, 'aspect_ratios.0.min_width'), + hmin: deepAccess(assetParams, 'aspect_ratios.0.min_height') + }); + break; + default: + return; } - } - }; - - this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { - let impressionBidRequestObject = {}; - impressionBidRequestObject.secure = 1; - if (requestParameters.requestId) { - impressionBidRequestObject.id = requestParameters.requestId; - } else { - impressionBidRequestObject.id = getUniqueIdentifierStr(); - } - if (requestParameters.domain) { - impressionBidRequestObject.domain = requestParameters.domain; - } - if (requestParameters.page) { - impressionBidRequestObject.page = requestParameters.page; - } - if (requestParameters.ref) { - impressionBidRequestObject.ref = requestParameters.ref; - } - if (requestParameters.callback) { - impressionBidRequestObject.callback = requestParameters.callback; - } - if (requestParameters.libVersion) { - impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION; - } - if (requestParameters.referrer) { - impressionBidRequestObject.referrer = requestParameters.referrer; - } - if (requestParameters.gdpr || requestParameters.gdpr === 0) { - impressionBidRequestObject.gdpr = requestParameters.gdpr; - } - if (requestParameters.usPrivacy) { - impressionBidRequestObject.us_privacy = requestParameters.usPrivacy; - } - if (requestParameters.schain) { - impressionBidRequestObject.schain = requestParameters.schain; - } - if (requestParameters.pagecat) { - impressionBidRequestObject.pagecat = requestParameters.pagecat; - } - if (requestParameters.genre) { - impressionBidRequestObject.genre = requestParameters.genre; - } - if (requestParameters.user) { - impressionBidRequestObject.user = requestParameters.user; - } - if (extraRequestParameters) { - for (let prop in extraRequestParameters) { - impressionBidRequestObject[prop] = extraRequestParameters[prop]; + request.assets.push(asset); } } + return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; + }, - if (requestParameters.coppa) { - impressionBidRequestObject.coppa = 1; - } + isOutstreamVideo(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream'; + }, - return impressionBidRequestObject; - }; + getBidFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return null; + } + const floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; + }, - this.createImpressionObject = function(placementObject) { - let outputObject = {}; - let impressionObject = {}; - outputObject.impressionObject = impressionObject; + buildSiteOrApp(request, bidderRequest) { + const app = {}; + const configAppSettings = config.getConfig('app') || {}; + const fpdAppSettings = config.getConfig('ortb2.app') || {}; + mergeDeep(app, configAppSettings, fpdAppSettings); - if (placementObject.id) { - impressionObject.id = placementObject.id; + if (Object.keys(app).length !== 0) { + request.app = app; } else { - impressionObject.id = getUniqueIdentifierStr(); - } - if (placementObject.adTypes) { - impressionObject.ad_types = placementObject.adTypes; - } - if (placementObject.adUnitId) { - outputObject.adUnitId = placementObject.adUnitId; - } - if (placementObject.currency) { - impressionObject.currency = placementObject.currency.toUpperCase(); - } - if (placementObject.bidFloor) { - impressionObject.bidfloor = placementObject.bidFloor; - } - if (placementObject.bidFloorCur) { - impressionObject.bidfloorcur = placementObject.bidFloorCur.toUpperCase(); - } - if (placementObject.placementId) { - impressionObject.pid = placementObject.placementId; - } - if (placementObject.publisherId) { - impressionObject.pubid = placementObject.publisherId; - } - if (placementObject.placementKey) { - impressionObject.pkey = placementObject.placementKey; - } - if (placementObject.transactionId) { - impressionObject.tid = placementObject.transactionId; - } - if (!isEmpty(placementObject.video)) { - const video = Object.assign({}, placementObject.video); - // skip must be 0 or 1 - if (video.skip !== 1) { - delete video.skipmin; - delete video.skipafter; - if (video.skip !== 0) { - logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); - delete video.skip; - } - } - if (!isEmpty(video)) { - impressionObject.video = video; + const site = {}; + const url = config.getConfig('pageUrl') || deepAccess(bidderRequest, 'refererInfo.referer'); + if (url) { + site.page = url; + site.domain = parseUrl(url).hostname; } + const configSiteSettings = config.getConfig('site') || {}; + const fpdSiteSettings = config.getConfig('ortb2.site') || {}; + mergeDeep(site, configSiteSettings, fpdSiteSettings); + request.site = site; } - if (placementObject.keyValues) { - for (let key in placementObject.keyValues) { - for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { - impressionObject.kvw = impressionObject.kvw || {}; - impressionObject.kvw[key] = impressionObject.kvw[key] || []; - impressionObject.kvw[key].push(placementObject.keyValues[key][valueCounter]); - } + }, +}; + +const ID_RESPONSE = { + buildAd(bid, bidRequest, bidResponse) { + if (bidRequest.mediaTypes && Object.keys(bidRequest.mediaTypes).length === 1) { + if (deepAccess(bidRequest, 'mediaTypes.video')) { + this.buildVideoAd(bid, bidRequest, bidResponse); + } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { + this.buildBannerAd(bid, bidRequest, bidResponse); + } else if (deepAccess(bidRequest, 'mediaTypes.native')) { + this.buildNativeAd(bid, bidRequest, bidResponse) + } + } else { + if (bidResponse.adm.search(/^ sizePair.length === 2 && - isInteger(sizePair[0]) && - isInteger(sizePair[1]) && - sizePair[0] >= 0 && - sizePair[1] >= 0) - .map(sizePair => { - return { w: sizePair[0], h: sizePair[1] } - }); - if (format.length > 0) { - impressionObject.banner.format = format; + buildBannerAd(bid, bidRequest, bidResponse) { + bid.mediaType = BANNER; + bid.ad = bidResponse.adm; + bid.width = bidResponse.w; + bid.height = bidResponse.h; + }, + + buildNativeAd(bid, bidRequest, bidResponse) { + bid.mediaType = NATIVE; + const nativeResponse = JSON.parse(bidResponse.adm); + const nativeAd = { + clickUrl: deepAccess(nativeResponse, 'link.url'), + clickTrackers: deepAccess(nativeResponse, 'link.clicktrackers'), + privacyLink: nativeResponse.privacy + } + // Trackers + if (nativeResponse.eventtrackers) { + nativeAd.impressionTrackers = []; + nativeResponse.eventtrackers.forEach(tracker => { + // Only handle impression event. Viewability events are not supported yet. + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + nativeAd.impressionTrackers.push(tracker.url); + break; + case 2: // js + // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + nativeAd.javascriptTrackers = ``; + break; + } + }); + } else { + nativeAd.impressionTrackers = nativeResponse.imptrackers || []; + nativeAd.javascriptTrackers = nativeResponse.jstracker; + } + nativeResponse.assets.map(asset => { + const assetParams = NATIVE_DATA.getAssetById(asset.id); + switch (assetParams.assetType) { + case NATIVE_DATA.ASSET_TYPES.TITLE: + nativeAd.title = asset.title.text; + break; + case NATIVE_DATA.ASSET_TYPES.DATA: + nativeAd[assetParams.name] = asset.data.value; + break; + case NATIVE_DATA.ASSET_TYPES.IMG: + nativeAd[assetParams.name] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; } + }); + bid.native = nativeAd; + }, +}; + +const ID_OUTSTREAM = { + RENDERER_URL: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + createRenderer(bidRequest) { + const renderer = Renderer.install({ + id: bidRequest.adUnitCode, + url: this.RENDERER_URL, + config: deepAccess(bidRequest, 'renderer.options'), + adUnitCode: bidRequest.adUnitCode + }); + try { + renderer.setRender(this.render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); } + return renderer; + }, + + render(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, ID_OUTSTREAM.handleRendererEvents.bind(null, bid)); + }); + }, + + handleRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); + }, +}; - if (!impressionObject.pid && - !impressionObject.pubid && - !impressionObject.pkey && - !(impressionObject.banner && impressionObject.banner.w && impressionObject.banner.h)) { - outputObject.impressionObject = null; - outputObject.errorCode = this.CONSTANTS.ERROR_CODES.MISSING_PLACEMENT_PARAMS; +const ID_RAZR = { + RENDERER_URL: 'https://razr.improvedigital.com/renderer.js', + addBidData({bid, bidRequest}) { + if (this.isValidBid(bid)) { + bid.renderer = Renderer.install({ + url: this.RENDERER_URL, + config: {bidRequest} + }); + bid.renderer.setRender(this.render); } - return outputObject; - }; -} + }, + + isValidBid(bid) { + return bid && /razr:\/\//.test(bid.ad); + }, + + render(bid) { + const {bidRequest} = bid.renderer.getConfig(); + + const payload = { + type: 'prebid', + bidRequest, + bid, + config: mergeDeep( + {}, + config.getConfig('improvedigital.rendererConfig'), + deepAccess(bidRequest, 'params.rendererConfig') + ) + }; + + const razr = window.razr = window.razr || {}; + razr.queue = razr.queue || []; + razr.queue.push(payload); + } +}; + +const ID_UTIL = { + toBit(val) { + return val ? 1 : 0; + }, +}; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 64c276ce806..9dcc11f5aa1 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai'; -import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter.js'; +import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; describe('Improve Digital Adapter Tests', function () { - const idClient = new ImproveDigitalAdServerJSClient('hb'); - - const METHOD = 'GET'; - const URL = 'https://ice.360yield.com/hb'; - const PARAM_PREFIX = 'jsonp='; + const METHOD = 'POST'; + const URL = 'https://ad.360yield.com/pb'; + const INSTREAM_TYPE = 1; + const OUTSTREAM_TYPE = 3; const simpleBidRequest = { bidder: 'improvedigital', @@ -22,10 +22,10 @@ describe('Improve Digital Adapter Tests', function () { auctionId: '192721e36a0239', mediaTypes: { banner: { - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] } }, - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] }; const videoParams = { @@ -53,7 +53,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatBidRequest = utils.deepClone(simpleBidRequest); multiFormatBidRequest.mediaTypes = { banner: { - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] }, video: { context: 'outstream', @@ -145,66 +145,85 @@ describe('Improve Digital Adapter Tests', function () { describe('buildRequests', function () { it('should make a well-formed request objects', function () { - const requests = spec.buildRequests([simpleBidRequest], bidderRequest); - expect(requests).to.be.an('array'); - expect(requests.length).to.equal(1); - - const request = requests[0]; + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(URL); expect(request.bidderRequest).to.deep.equal(bidderRequest); - expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); - - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request).to.be.an('object'); - expect(params.bid_request.id).to.be.a('string'); - expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); - expect(params.bid_request.gdpr).to.not.exist; - expect(params.bid_request.us_privacy).to.not.exist; - expect(params.bid_request.schain).to.not.exist; - expect(params.bid_request.user).to.not.exist; - expect(params.bid_request.imp).to.deep.equal([ + + const payload = JSON.parse(request.data); + expect(payload).to.be.an('object'); + expect(payload.id).to.be.a('string'); + expect(payload.tmax).not.to.exist; + expect(payload.cur).to.be.an('array'); + expect(payload.regs).to.not.exist; + expect(payload.schain).to.not.exist; + expect(payload.source).to.be.an('object'); + expect(payload.device).to.be.an('object'); + expect(payload.user).to.not.exist; + expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', - pid: 1053688, - tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', - banner: {} + secure: 0, + ext: { + bidder: { + placementId: 1053688, + } + }, + banner: { + format: [ + {w: 300, h: 250}, + {w: 160, h: 600}, + ] + } } ]); + getConfigStub.restore(); }); it('should make a well-formed request object for multi-format ad unit', function () { - const requests = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest); - expect(requests).to.be.an('array'); - expect(requests.length).to.equal(1); - - const request = requests[0]; + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest)[0]; + expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(URL); expect(request.bidderRequest).to.deep.equal(multiFormatBidderRequest); - expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); - - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request).to.be.an('object'); - expect(params.bid_request.id).to.be.a('string'); - expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); - expect(params.bid_request.gdpr).to.not.exist; - expect(params.bid_request.us_privacy).to.not.exist; - expect(params.bid_request.imp).to.deep.equal([ + + const payload = JSON.parse(request.data); + expect(payload).to.be.an('object'); + expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', - pid: 1053688, - tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', - banner: {} + secure: 0, + ext: { + bidder: { + placementId: 1053688, + } + }, + video: { + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, + banner: { + format: [ + {w: 300, h: 250}, + {w: 160, h: 600}, + ] + } } ]); + getConfigStub.restore(); }); it('should set placementKey and publisherId for smart tags', function () { - const requests = spec.buildRequests([simpleSmartTagBidRequest], bidderRequest); - const params = JSON.parse(decodeURIComponent(requests[0].data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].pubid).to.equal(1032); - expect(params.bid_request.imp[0].pkey).to.equal('data_team_test_hb_smoke_test'); + const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); + expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); + expect(payload.imp[0].ext.bidder.placementKey).to.equal('data_team_test_hb_smoke_test'); }); it('should add keyValues', function () { @@ -215,102 +234,92 @@ describe('Improve Digital Adapter Tests', function () { ] }; bidRequest.params.keyValues = keyValues; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].ext.bidder.keyValues).to.deep.equal(keyValues); }); - it('should add single size filter', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - const size = { - w: 800, - h: 600 - }; - bidRequest.params.size = size; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal(size); - // When single size filter is set, format shouldn't be populated. This - // is to maintain backward compatibily - expect(params.bid_request.imp[0].banner.format).to.not.exist; - }); + // it('should add single size filter', function () { + // const bidRequest = Object.assign({}, simpleBidRequest); + // const size = { + // w: 800, + // h: 600 + // }; + // bidRequest.params.size = size; + // const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data); + // expect(payload.imp[0].banner).to.deep.equal(size); + // // When single size filter is set, format shouldn't be populated. This + // // is to maintain backward compatibily + // expect(payload.imp[0].banner.format).to.not.exist; + // }); it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].currency).to.equal('JPY'); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.cur).to.deep.equal(['JPY']); getConfigStub.restore(); }); it('should add bid floor', function () { const bidRequest = Object.assign({}, simpleBidRequest); - let request = spec.buildRequests([bidRequest], bidderRequest)[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); // Floor price currency shouldn't be populated without a floor price - expect(params.bid_request.imp[0].bidfloorcur).to.not.exist; + expect(payload.imp[0].bidfloorcur).to.not.exist; // Default floor price currency bidRequest.params.bidFloor = 0.05; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); // Floor price currency bidRequest.params.bidFloorCur = 'eUR'; - request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('EUR'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + expect(payload.imp[0].bidfloorcur).to.equal('EUR'); // getFloor defined -> use it over bidFloor let getFloorResponse = { currency: 'USD', floor: 3 }; bidRequest.getFloor = () => getFloorResponse; - request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(3); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(3); + // expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestGdpr)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(params.bid_request.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(payload.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); it('should add CCPA consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], { uspConsent: '1YYY' })[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.us_privacy).to.equal('1YYY'); + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }}); + const payload = JSON.parse(request[0].data); + expect(payload.regs.ext.us_privacy).to.equal('1YYY'); }); it('should add referrer', function () { const bidRequest = Object.assign({}, simpleBidRequest); const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); + const payload = JSON.parse(request.data); + expect(payload.site.page).to.equal('https://blah.com/test.html'); }); it('should not add video params for banner', function () { const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.not.exist; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.not.exist; }); - it('should add ad type for instream video', function () { + it('should add correct placement value for instream and outstream video', function () { let bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); - bidRequest.mediaType = 'video'; - let request = spec.buildRequests([bidRequest], bidderRequest)[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); - expect(params.bid_request.imp[0].video).to.not.exist; + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video).to.not.exist; bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaTypes = { @@ -319,32 +328,45 @@ describe('Improve Digital Adapter Tests', function () { playerSize: [640, 480] } }; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); - expect(params.bid_request.imp[0].video).to.not.exist; - }); - - it('should not set ad type for outstream video', function() { - const request = spec.buildRequests([outstreamBidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.not.exist; - expect(params.bid_request.imp[0].video).to.not.exist; - }); - - it('should not set ad type for multi-format bids', function() { - const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.not.exist; - expect(params.bid_request.imp[0].video).to.not.exist; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(1); + bidRequest.mediaTypes.video.context = 'outstream'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(3); }); it('should set video params for instream', function() { const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + delete bidRequest.mediaTypes.video.playerSize; + const videoParams = { + mimes: ['video/mp4'], + skip: 1, + skipmin: 5, + skipafter: 30, + minduration: 15, + maxduration: 60, + startdelay: 5, + minbitrate: 500, + maxbitrate: 2000, + w: 1024, + h: 640, + placement: INSTREAM_TYPE, + }; bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set video playerSize over video params', () => { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + bidRequest.params.video = { + w: 1024, h: 640 + } + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.h).equal(480); + expect(payload.imp[0].video.w).equal(640); }); it('should set skip params only if skip=1', function() { @@ -357,22 +379,28 @@ describe('Improve Digital Adapter Tests', function () { } bidRequest.params.video = videoTest; let request = spec.buildRequests([bidRequest])[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.skip).to.equal(1); + expect(payload.imp[0].video.skipmin).to.equal(5); + expect(payload.imp[0].video.skipafter).to.equal(30); // 0 - leave out skipmin and skipafter videoTest.skip = 0; bidRequest.params.video = videoTest; request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal({ skip: 0 }); + payload = JSON.parse(request.data); + expect(payload.imp[0].video.skip).to.equal(0); + expect(payload.imp[0].video.skipmin).to.not.exist; + expect(payload.imp[0].video.skipafter).to.not.exist; // other videoTest.skip = 'blah'; bidRequest.params.video = videoTest; request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.not.exist; + payload = JSON.parse(request.data); + expect(payload.imp[0].video.skip).to.not.exist; + expect(payload.imp[0].video.skipmin).to.not.exist; + expect(payload.imp[0].video.skipafter).to.not.exist; }); it('should ignore invalid/unexpected video params', function() { @@ -387,51 +415,36 @@ describe('Improve Digital Adapter Tests', function () { videoTestInvParam.blah = 1; bidRequest.params.video = videoTestInvParam; let request = spec.buildRequests([bidRequest])[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.blah).not.to.exist; }); it('should set video params for outstream', function() { const bidRequest = JSON.parse(JSON.stringify(outstreamBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal({...{ + mimes: ['video/mp4'], + placement: OUTSTREAM_TYPE, + w: bidRequest.mediaTypes.video.playerSize[0], + h: bidRequest.mediaTypes.video.playerSize[1], + }, + ...videoParams}); }); - + // it('should set video params for multi-format', function() { const bidRequest = JSON.parse(JSON.stringify(multiFormatBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); - }); - - it('should not set Prebid sizes in bid request for instream video', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([instreamBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); - }); - - it('should not set Prebid sizes in bid request for outstream video', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([outstreamBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); - }); - - it('should not set Prebid sizes in multi-format bid request', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); + const payload = JSON.parse(request.data); + const testVideoParams = Object.assign({ + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, videoParams); + expect(payload.imp[0].video).to.deep.equal(testVideoParams); }); it('should add schain', function () { @@ -439,8 +452,8 @@ describe('Improve Digital Adapter Tests', function () { const bidRequest = Object.assign({}, simpleBidRequest); bidRequest.schain = schain; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.schain).to.equal(schain); + const payload = JSON.parse(request.data); + expect(payload.source.ext.schain).to.equal(schain); }); it('should add eids', function () { @@ -455,8 +468,8 @@ describe('Improve Digital Adapter Tests', function () { const bidRequest = Object.assign({}, simpleBidRequest); bidRequest.userId = userId; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.user).to.deep.equal(expectedUserObject); + const payload = JSON.parse(request.data); + expect(payload.user).to.deep.equal(expectedUserObject); }); it('should return 2 requests', function () { @@ -486,8 +499,8 @@ describe('Improve Digital Adapter Tests', function () { const getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal({ + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner).to.deep.equal({ format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -506,8 +519,8 @@ describe('Improve Digital Adapter Tests', function () { }; bidRequest.params.size = size; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal({ + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner).to.deep.equal({ format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -516,434 +529,396 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub.restore(); }); - it('should set pagecat and genre ➞ fpd:ortb2.site', function() { - config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2"],"pagecat":["IAB2-2"],"content":{"genre":"Adventure"}}}}')); - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.pagecat).to.be.an('array'); - expect(params.bid_request.pagecat).to.deep.equal(['IAB2-2']); - expect(params.bid_request.genre).to.be.a('string'); - expect(params.bid_request.genre).be.equal('Adventure'); - }); + it('should set GPID and Instl Signal', function () { + const bidRequest = Object.assign({ + ortb2Imp: { + instl: true, + ext: { + gpid: '/123/ID-FORMAT', + data: { + pbadslot: '/123/ID-FORMAT-PBADSLOT', + adserver: { + adslot: '/123/ID-FORMAT-ADSERVER-PB-ADSLOT', + } + } + }, + } + }, simpleBidRequest); + let request = spec.buildRequests([bidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT'); + expect(payload.imp[0].instl).to.equal(1); - it('should not set pagecat and genre when malformed data provided ➞ fpd:ortb2.site', function() { - config.setConfig(JSON.parse('{"ortb2":{"site":{"pagecat":"IAB2-2","content":{"genre":["Adventure"]}}}}')); - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.pagecat).does.not.exist; - expect(params.bid_request.genre).does.not.exist; + delete bidRequest.ortb2Imp.ext.gpid; + request = spec.buildRequests([bidRequest], bidderRequest)[0]; + payload = JSON.parse(request.data); + expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT-PBADSLOT'); + + delete bidRequest.ortb2Imp.ext.data.pbadslot; + request = spec.buildRequests([bidRequest], bidderRequest)[0]; + payload = JSON.parse(request.data); + expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT-ADSERVER-PB-ADSLOT'); + + delete bidRequest.ortb2Imp.ext.data.adserver; + delete bidRequest.ortb2Imp.instl; + request = spec.buildRequests([bidRequest], bidderRequest)[0]; + payload = JSON.parse(request.data); + expect(payload.imp[0].ext.gpid).to.not.exist; + expect(payload.imp[0].instl).to.not.exist; }); - it('should use cat when pagecat not available ➞ fpd:ortb2.site', function() { - config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2"]}}}')); - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.pagecat).to.be.an('array'); - expect(params.bid_request.pagecat).to.deep.equal(['IAB2']); + it('should not set site when app is defined in FPD', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('ortb2.app').returns({ content: 'XYZ' }); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.not.exist; + expect(payload.app).does.exist; + expect(payload.app.content).does.exist.and.equal('XYZ'); + getConfigStub.restore(); }); - it('should format pagecat correctly ➞ fpd:ortb2.site', function() { - config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2", ["IAB-1"], "IAB3", 123, ""]}}}')); - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.pagecat).to.be.an('array'); - expect(params.bid_request.pagecat).to.deep.equal([ - 'IAB2', - 'IAB3' - ] - ); + it('should not set site when app is defined in CONFIG', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('app').returns({ content: 'XYZ' }); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.not.exist; + expect(payload.app).does.exist; + expect(payload.app.content).does.exist.and.equal('XYZ'); + getConfigStub.restore(); }); - it('should set coppa', function() { - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.coppa).to.equal(1); + it('should set correct site params', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('site').returns({ + content: 'XYZ', + page: 'https://improveditigal.com/', + domain: 'improveditigal.com' + }); + let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + let payload = JSON.parse(request.data); + expect(payload.site.content).does.exist.and.equal('XYZ'); + expect(payload.site.page).does.exist.and.equal('https://improveditigal.com/'); + expect(payload.site.domain).does.exist.and.equal('improveditigal.com'); + getConfigStub.reset(); + + request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + payload = JSON.parse(request.data); + expect(payload.site.content).does.not.exist; + expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); + expect(payload.site.domain).does.exist.and.equal('blah.com'); + + getConfigStub.withArgs('ortb2.site').returns({ + content: 'ZZZ', + }); + request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + payload = JSON.parse(request.data); + expect(payload.site.content).does.exist.and.equal('ZZZ'); + expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); + expect(payload.site.domain).does.exist.and.equal('blah.com'); + getConfigStub.restore(); + }); - config.getConfig.restore(); + it('should set pageUrl as site param', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('pageUrl').returns('https://improvidigital.com/test-page'); + let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + let payload = JSON.parse(request.data); + expect(payload.site.page).does.exist.and.equal('https://improvidigital.com/test-page'); + expect(payload.site.domain).does.exist.and.equal('improvidigital.com'); + getConfigStub.reset(); + + getConfigStub.withArgs('pageUrl').returns(undefined); + request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + payload = JSON.parse(request.data); + expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); + expect(payload.site.domain).does.exist.and.equal('blah.com'); + getConfigStub.restore(); }); - it('should undefined coppa', function() { - const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.coppa).to.equal(undefined); + it('should set site when app not available', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('app').returns(undefined); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.exist; + expect(payload.app).does.not.exist; + getConfigStub.restore(); }); }); const serverResponse = { 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ + 'id': '99f9a9e6-5126-425b-822c-8b4edad2a719', + 'cur': 'EUR', + 'seatbid': [ { - 'isNet': false, - 'id': '33e9500b21129f', - 'advid': '5279', - 'price': 1.45888594164456, - 'nurl': 'https://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 290, - 'pid': 1053688, - 'sync': [ - 'https://link1', - 'https://link2' - ], - 'crid': '422031', - 'w': 600, - 'cid': '99006', - 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' - } - ], - 'debug': '' - } - }; - - const serverResponseRazr = { - 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ - { - 'isNet': false, - 'id': '33e9500b21129f', - 'advid': '5279', - 'price': 1.45888594164456, - 'nurl': 'https://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 290, - 'pid': 1053688, - 'sync': [ - 'https://link1', - 'https://link2' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 320896, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '510265', + 'price': 1.9200543539802946, + 'id': '35adfe19-d6e9-46b9-9f7d-20da7026b965', + 'w': 728, + 'impid': '33e9500b21129f', + 'h': 90, + 'adm': '  ', + 'cid': '123159' + } ], - 'crid': '422031', - 'w': 600, - 'cid': '99006', - 'adm': 'document.writeln("', - privacy: 'https://www.myprivacyurl.com' - } + 'crid': '544456', + 'exp': 120, + 'id': '52098fad-20c1-476b-a4fa-41e275e5a4a5', + 'price': 1.8600000000000003, + 'adm': "{\"ver\":\"1.1\",\"imptrackers\":[\"https://secure.adnxs.com/imptr?id=52311&t=2\",\"https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ==\"],\"assets\":[{\"id\":1,\"data\":{\"value\":\"ImproveDigital\",\"type\":1}},{\"id\":3,\"data\":{\"value\":\"Test content.\",\"type\":2}},{\"id\":0,\"title\":{\"text\":\"Sample Prebid Test Title\"}}],\"link\":{\"url\":\"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F\"},\"jstracker\":\"\"}", + 'impid': '2d7a7db325c6f', + 'cid': '196108' + } + ], + 'seat': 'improvedigital' } - ], - debug: '' + ] } }; const serverResponseVideo = { 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ + 'id': '8ed20675-8934-430c-b645-1ccd17b35839', + 'cur': 'EUR', + 'seatbid': [ { - 'isNet': false, - 'id': '33e9500b21129f', - 'advid': '5279', - 'price': 1.45888594164456, - 'nurl': 'http://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 290, - 'pid': 1053688, - 'sync': [ - 'http://link1', - 'http://link2' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 321329, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '484367', + 'price': 9.600271769901472, + 'id': 'b131fd7b-5759-4b72-800e-60e69291e7d9', + 'adomain': [ + 'improvedigital.com' + ], + 'impid': '33e9500b21129f', + 'adm': '', + 'w': 640, + 'h': 480, + 'cid': '123159' + } ], - 'crid': '422031', - 'w': 600, - 'cid': '99006', - 'adm': '', - 'ad_type': 'video' + 'seat': 'improvedigital' } ], - 'debug': '' } }; - const nativeEventtrackers = [ - { - event: 1, - method: 1, - url: 'https://www.mytracker.com/imptracker' - }, - { - event: 1, - method: 2, - url: 'https://www.mytracker.com/tracker.js' + const serverResponseRazr = { + body: { + 'id': '2adac6a5fe04df', + 'cur': 'EUR', + 'ext': { + 'improvedigital': { + 'sync': [ + 'https://d5p.de17a.com/getuid/improve_digital?publisher_user_id=ce26f11e-567a-4eb7-bf94-51752e293ca5&publisher_dsp_id=61&publisher_call_type=redirect&gdpr=1&gdpr_consent=CPU22FrPU22FrAcABBENCDCsAP_AAH_AAChQIltf_X__b3_j-_5_f_t0eY1P9_7_v-0zjhfdt-8N3f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbO2dYGH9_n93TuZKY7______z_v-v_v____f_7-3_3__5_3---_e_V_99zLv9____39nP___9v-_9_____4IhgEmGpeQBdmWODJtGlUKIEYVhIdAKACigGFoisIHVwU7K4CfUELABCagJwIgQYgowYBAAIJAEhEQEgB4IBEARAIAAQAqwEIACNgEFgBYGAQACgGhYgRQBCBIQZHBUcpgQFSLRQT2ViCUHexphCGWeBFAo_oqEBGs0QLAyEhYOY4AkBLxZIHmKF8gAAAAA.f_gAD_gAAAAA&publisher_redirecturl=https://euw-ice.360yield.com/match' + ] + } + }, + 'seatbid': [ + { + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 410573, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '544063', + 'price': 1.9199364935359489, + 'id': '1fcf4dd8-a783-48ed-b59c-8fc8eeccb024', + 'adomain': [ + 'improvedigital.com' + ], + 'w': 970, + 'impid': '33e9500b21129f', + 'h': 250, + 'adm': '\n\n\n\n\n\n\n\n ', + 'cid': '187354' + } + ], + 'seat': 'improvedigital' + } + ] } - ]; + }; describe('interpretResponse', function () { const expectedBid = [ { - 'ad': '', - 'creativeId': '422031', - 'cpm': 1.45888594164456, - 'currency': 'USD', - 'height': 290, - 'mediaType': 'banner', - 'netRevenue': false, - 'requestId': '33e9500b21129f', - 'ttl': 300, - 'width': 600 + requestId: '33e9500b21129f', + cpm: 1.9200543539802946, + currency: 'EUR', + width: 728, + height: 90, + ttl: 300, + ad: '  ', + creativeId: '510265', + dealId: 320896, + netRevenue: false, + mediaType: BANNER, + meta: { + advertiserDomains: [] + } } ]; const expectedTwoBids = [ expectedBid[0], { - 'ad': '', - 'creativeId': '422033', - 'cpm': 1.23, - 'currency': 'USD', - 'height': 400, - 'mediaType': 'banner', - 'netRevenue': true, - 'requestId': '1234', - 'ttl': 300, - 'width': 700 - } - ]; - - const expectedBidNative = [ - { - mediaType: 'native', - creativeId: '422031', - cpm: 1.45888594164456, - currency: 'USD', - height: 290, - netRevenue: false, requestId: '33e9500b21129f', + cpm: 1.9200543539802946, + currency: 'EUR', + width: 300, + height: 250, ttl: 300, - width: 600, - native: { - title: 'Native title', - body: 'Native body', - body2: 'body2', - cta: 'Do it', - sponsoredBy: 'Improve Digital', - rating: '4', - likes: '10105', - downloads: '150000', - price: '3.99', - salePrice: '4.49', - phone: '(123) 456-7890', - address: '123 Main Street, Anywhere USA', - displayUrl: 'https://myurl.com', - icon: { - url: 'https://blah.com/icon.jpg', - height: 30, - width: 40 - }, - image: { - url: 'https://blah.com/image.jpg', - height: 200, - width: 800 - }, - clickUrl: 'https://advertiser.com', - clickTrackers: ['https://click.tracker.com/click?impid=123'], - impressionTrackers: [ - 'https://ice.360yield.com/imp_pixel?ic=wVm', - 'https://imptrack1.com', - 'https://imptrack2.com' - ], - javascriptTrackers: '', - privacyLink: 'https://www.myprivacyurl.com' + ad: '  ', + creativeId: '479163', + dealId: 320896, + netRevenue: false, + mediaType: BANNER, + meta: { + advertiserDomains: [] } } ]; const expectedBidInstreamVideo = [ { - 'vastXml': '', - 'creativeId': '422031', - 'cpm': 1.45888594164456, - 'currency': 'USD', - 'height': 290, - 'mediaType': 'video', - 'netRevenue': false, - 'requestId': '33e9500b21129f', - 'ttl': 300, - 'width': 600 + requestId: '33e9500b21129f', + cpm: 9.600271769901472, + currency: 'EUR', + ttl: 300, + vastXml: '', + creativeId: '484367', + dealId: 321329, + netRevenue: false, + mediaType: VIDEO, + meta: { + advertiserDomains: ['improvedigital.com'], + } } ]; const expectedBidOutstreamVideo = utils.deepClone(expectedBidInstreamVideo); expectedBidOutstreamVideo[0].adResponse = { - content: expectedBidOutstreamVideo[0].vastXml, - height: expectedBidOutstreamVideo[0].height, - width: expectedBidOutstreamVideo[0].width + content: expectedBidOutstreamVideo[0].vastXml }; it('should return a well-formed display bid', function () { @@ -965,50 +940,35 @@ describe('Improve Digital Adapter Tests', function () { const response = JSON.parse(JSON.stringify(serverResponse)); let bids; - delete response.body.bid[0].lid; - response.body.bid[0].buying_type = 'deal_id'; + delete response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id; + response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'deal_id'; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - delete response.body.bid[0].buying_type; + response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; + delete response.body.seatbid[0].bid[0].ext.improvedigital.buying_type; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'rtb'; + response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; + response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'rtb'; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'classic'; + response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; + response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'classic'; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.equal(268515); - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'deal_id'; + response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; + response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'deal_id'; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].dealId).to.equal(268515); - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = 'deal_id'; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.not.exist; - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = [ 'deal_id', 'classic' ]; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.not.exist; - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = [ 'rtb', 'deal_id', 'deal_id' ]; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.equal(12456); }); it('should set currency', function () { const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].currency = 'eur'; + response.body.cur = 'eur'; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].currency).to.equal('EUR'); }); @@ -1018,35 +978,35 @@ describe('Improve Digital Adapter Tests', function () { let bids; // Price missing or 0 - response.body.bid[0].price = 0; + response.body.seatbid[0].bid[0].price = 0; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - delete response.body.bid[0].price; + delete response.body.seatbid[0].bid[0]; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - response.body.bid[0].price = null; + response.body.seatbid[0].bid[0] = []; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // errorCode present response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].errorCode = undefined; + response.body.seatbid[0].bid[0].errorCode = undefined; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // adm and native missing response = JSON.parse(JSON.stringify(serverResponse)); - delete response.body.bid[0].adm; + delete response.body.seatbid[0].bid[0].adm; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - response.body.bid[0].adm = null; + response.body.seatbid[0].bid[0].adm = null; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); }); it('should set netRevenue', function () { const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].isNet = true; + response.body.seatbid[0].bid[0].ext.improvedigital.is_net = true; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].netRevenue).to.equal(true); }); @@ -1054,30 +1014,31 @@ describe('Improve Digital Adapter Tests', function () { it('should set advertiserDomains', function () { const adomain = ['domain.com']; const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].adomain = adomain; + response.body.seatbid[0].bid[0].adomain = adomain; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].meta.advertiserDomains).to.equal(adomain); }); - + // // Native ads it('should return a well-formed native ad bid', function () { - let bids = spec.interpretResponse(serverResponseNative, {bidderRequest}); - expect(bids[0].ortbNative).to.deep.equal(serverResponseNative.body.bid[0].native); - delete bids[0].ortbNative; - expect(bids).to.deep.equal(expectedBidNative); - - // eventtrackers - const response = JSON.parse(JSON.stringify(serverResponseNative)); - const expectedBids = JSON.parse(JSON.stringify(expectedBidNative)); - response.body.bid[0].native.eventtrackers = nativeEventtrackers; - expectedBids[0].native.impressionTrackers = [ - 'https://ice.360yield.com/imp_pixel?ic=wVm', - 'https://www.mytracker.com/imptracker' - ]; - expectedBids[0].native.javascriptTrackers = ''; - bids = spec.interpretResponse(response, {bidderRequest}); - delete bids[0].ortbNative; - expect(bids).to.deep.equal(expectedBids); + const nativeBidderRequest = JSON.parse(JSON.stringify(bidderRequest)); + nativeBidderRequest.bids[0].bidId = '2d7a7db325c6f'; + delete nativeBidderRequest.bids[0].mediaTypes.banner; + nativeBidderRequest.bids[0].mediaTypes.native = {}; + const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: nativeBidderRequest}); + // Verify Native Response + expect(bids[0].native).to.exist; + const nativeBid = bids[0].native; + const nativeResp = JSON.parse(serverResponseNative.body.seatbid[0].bid[0].adm); + // Verify Native Response + expect(nativeBid.clickUrl).to.exist.and.equal(nativeResp.link.url); + expect(nativeBid.impressionTrackers).to.exist.and.deep.equal(nativeResp.imptrackers); + expect(nativeBid.javascriptTrackers).to.exist.and.deep.equal(nativeResp.jstracker); + + // Verify Assets + expect(nativeBid.title).to.exist.and.equal('Sample Prebid Test Title'); + expect(nativeBid.sponsoredBy).to.exist.and.equal('ImproveDigital'); + expect(nativeBid.body).to.exist.and.equal('Test content.'); }); // Video