diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js new file mode 100644 index 00000000000..04f4085ba24 --- /dev/null +++ b/modules/boldwinBidAdapter.js @@ -0,0 +1,110 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'boldwin'; +const AD_URL = 'https://ssp.videowalldirect.com/?c=o&m=multi'; +const SYNC_URL = 'https://cs.videowalldirect.com/?c=o&m=cookie' + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + const len = validBidRequests.length; + + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + let sizes + if (bid.mediaTypes) { + if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes + } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + sizes = bid.mediaTypes[VIDEO].playerSize + } + } + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: sizes || [], + wPlayer: sizes ? sizes[0] : 0, + hPlayer: sizes ? sizes[1] : 0, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {} + }); + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: SYNC_URL + }]; + } +}; + +registerBidder(spec); diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md new file mode 100644 index 00000000000..4bf272c4de3 --- /dev/null +++ b/modules/boldwinBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: boldwin Bidder Adapter +Module Type: boldwin Bidder Adapter +``` + +# Description + +Module that connects to boldwin demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 1a5879a40ff..e4d5c12eb46 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -158,11 +158,6 @@ export function requestBidsHook(fn, reqBidsConfigObj) { timer: null }; - // in case we already have consent (eg during bid refresh) - if (consentData) { - return exitModule(null, hookConfig); - } - if (!uspCallMap[consentAPI]) { utils.logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index edaf2dc8e9a..77d4220e59a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -6,7 +6,7 @@ import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; -const SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BANNER_ENDPOINT_VERSION = 7.2; const VIDEO_ENDPOINT_VERSION = 8.1; diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 849954fa072..62958cfbfd2 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -4,7 +4,7 @@ import CONSTANTS from '../src/constants.json'; import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; -import { getPriceGranularity, AUCTION_IN_PROGRESS, AUCTION_COMPLETED } from '../src/auction.js' +import { AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity } from '../src/auction.js' const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -29,6 +29,7 @@ const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; +const BID_FLOOR_REJECTED = 12; const DUMMY_BIDDER = '-2'; const CONFIG_PENDING = 0; @@ -151,7 +152,7 @@ class PageDetail { this.canonical_url = canonicalUrl; this.og_url = ogUrl; this.twitter_url = twitterUrl; - this.screen = this._getWindowSize() + this.screen = this._getWindowSize(); } _getTopWindowReferrer() { @@ -201,9 +202,9 @@ class PageDetail { } class AdSlot { - constructor(mediaTypes, bannerSizes, tmax, supplyAdCode, adext) { + constructor(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize) { this.mediaTypes = mediaTypes; - this.bannerSizes = bannerSizes; + this.allMediaTypeSizes = allMediaTypeSizes; this.tmax = tmax; this.supplyAdCode = supplyAdCode; this.adext = adext; @@ -213,6 +214,8 @@ class AdSlot { // shouldBeLogged is assigned when requested, // since we are waiting for logging percent response this.shouldBeLogged = undefined; + this.context = context; + this.adSize = adSize; // old ad unit sizes } getShouldBeLogged() { @@ -226,10 +229,12 @@ class AdSlot { return Object.assign({ supcrid: this.supplyAdCode, mediaTypes: this.mediaTypes && this.mediaTypes.join('|'), - szs: this.bannerSizes.join('|'), + szs: this.allMediaTypeSizes.map(sz => sz.join('x')).join('|'), tmax: this.tmax, targ: JSON.stringify(this.targeting), - ismn: this.medianetPresent + ismn: this.medianetPresent, + vplcmtt: this.context, + sz2: this.adSize.map(sz => sz.join('x')).join('|'), }, this.adext && {'adext': JSON.stringify(this.adext)}, ); @@ -261,6 +266,8 @@ class Bid { this.crid = undefined; this.pubcrid = undefined; this.mpvid = undefined; + this.floorPrice = undefined; + this.floorRule = undefined; } get size() { @@ -290,6 +297,8 @@ class Bid { crid: this.crid, pubcrid: this.pubcrid, mpvid: this.mpvid, + bidflr: this.floorPrice, + flrrule: this.floorRule, ext: JSON.stringify(this.ext) } } @@ -306,6 +315,7 @@ class Auction { this.setTargetingTime = undefined; this.auctionEndTime = undefined; this.bidWonTime = undefined; + this.floorData = {}; } hasEnded() { @@ -319,13 +329,22 @@ class Auction { tts: this.setTargetingTime - this.auctionInitTime, wts: this.bidWonTime - this.auctionInitTime, aucstatus: this.status, - acid: this.acid + acid: this.acid, + flrdata: this._mergeFieldsToLog({ + ln: this.floorData.location, + skp: this.floorData.skipped, + enfj: utils.deepAccess(this.floorData, 'enforcements.enforceJS'), + enfd: utils.deepAccess(this.floorData, 'enforcements.floorDeals'), + sr: this.floorData.skipRate, + fs: this.floorData.fetchStatus + }), + flrver: this.floorData.modelVersion } } - addSlot(supplyAdCode, { mediaTypes, bannerSizes, tmax, adext }) { + addSlot(supplyAdCode, { mediaTypes, allMediaTypeSizes, tmax, adext, context, adSize }) { if (supplyAdCode && this.adSlots[supplyAdCode] === undefined) { - this.adSlots[supplyAdCode] = new AdSlot(mediaTypes, bannerSizes, tmax, supplyAdCode, adext); + this.adSlots[supplyAdCode] = new AdSlot(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize); this.addBid( new Bid('-1', DUMMY_BIDDER, 'client', '-1', supplyAdCode) ); @@ -351,13 +370,27 @@ class Auction { getWinnerAdslotBid(adslot) { return this.getAdslotBids(adslot).filter((bid) => bid.winner); } + + _mergeFieldsToLog(objParams) { + let logParams = []; + let value; + for (const param of Object.keys(objParams)) { + value = objParams[param]; + logParams.push(param + '=' + (value === undefined ? '' : value)); + } + return logParams.join('||'); + } } -function auctionInitHandler({auctionId, timestamp}) { +function auctionInitHandler({auctionId, timestamp, bidderRequests}) { if (auctionId && auctions[auctionId] === undefined) { auctions[auctionId] = new Auction(auctionId); auctions[auctionId].auctionInitTime = timestamp; } + const floorData = utils.deepAccess(bidderRequests, '0.bids.0.floorData'); + if (floorData) { + auctions[auctionId].floorData = {...floorData}; + } } function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, uspConsent, gdpr }) { @@ -375,13 +408,16 @@ function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, us const { adUnitCode, bidder, mediaTypes, sizes, bidId, src } = bid; if (!auctions[auctionId].adSlots[adUnitCode]) { auctions[auctionId].auctionStartTime = auctionStart; + const sizeObject = _getSizes(mediaTypes, sizes); auctions[auctionId].addSlot( adUnitCode, Object.assign({}, (mediaTypes instanceof Object) && { mediaTypes: Object.keys(mediaTypes) }, - { bannerSizes: utils.deepAccess(mediaTypes, 'banner.sizes') || sizes || [] }, + { allMediaTypeSizes: [].concat(sizeObject.banner, sizeObject.native, sizeObject.video) }, { adext: utils.deepAccess(mediaTypes, 'banner.ext') || '' }, - { tmax: timeout } + { tmax: timeout }, + { context: utils.deepAccess(mediaTypes, 'video.context') || '' }, + { adSize: sizeObject.banner } ) ); } @@ -395,6 +431,17 @@ function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, us }); } +function _getSizes(mediaTypes, sizes) { + const banner = utils.deepAccess(mediaTypes, 'banner.sizes') || sizes || []; + const native = utils.deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; + const playerSize = utils.deepAccess(mediaTypes, 'video.playerSize') || []; + let video = []; + if (playerSize.length === 2) { + video = [playerSize] + } + return { banner, native, video } +} + function bidResponseHandler(bid) { const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; const {originalCpm, bidderCode, creativeId, adId, currency} = bid; @@ -411,6 +458,8 @@ function bidResponseHandler(bid) { { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, { adId, currency } ); + bidObj.floorPrice = utils.deepAccess(bid, 'floorData.floorValue'); + bidObj.floorRule = utils.deepAccess(bid, 'floorData.floorRule'); bidObj.originalCpm = originalCpm || cpm; let dfpbd = utils.deepAccess(bid, 'adserverTargeting.hb_pb'); if (!dfpbd) { @@ -419,7 +468,11 @@ function bidResponseHandler(bid) { dfpbd = bid[priceGranularityKey] || cpm; } bidObj.dfpbd = dfpbd; - bidObj.status = BID_SUCCESS; + if (bid.status === CONSTANTS.BID_STATUS.BID_REJECTED) { + bidObj.status = BID_FLOOR_REJECTED; + } else { + bidObj.status = BID_SUCCESS; + } if (bidderCode === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { Object.assign( diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 6280dd12268..fd66c8ce69f 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -4,7 +4,10 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import find from 'core-js-pure/features/array/find.js'; -const { registerBidder } = require('../src/adapters/bidderFactory.js'); +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const storage = getStorageManager(); const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -62,14 +65,15 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.userId) { payload.userId = bidderRequest.userId; } - if (window.localStorage) { - payload.onetagSid = window.localStorage.getItem('onetag_sid'); - } - const payloadString = JSON.stringify(payload); + try { + if (storage.hasLocalStorage()) { + payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); + } + } catch (e) {} return { method: 'POST', url: ENDPOINT, - data: payloadString + data: JSON.stringify(payload) } } @@ -161,17 +165,22 @@ function onetagRenderer({renderer, width, height, vastXml, adUnitCode}) { } function getFrameNesting() { - let frame = window; + let topmostFrame = window; + let parent = window.parent; + let currentFrameNesting = 0; try { - while (frame !== frame.top) { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; // eslint-disable-next-line no-unused-expressions - frame.location.href; - frame = frame.parent; + parent.location.href; + topmostFrame = topmostFrame.parent; } - } catch (e) {} + } catch (e) { + currentFrameNesting = parent === topmostFrame.top ? 1 : 2; + } return { - topmostFrame: frame, - currentFrameNesting: frame.top === frame ? 1 : 2 + topmostFrame, + currentFrameNesting } } @@ -198,8 +207,11 @@ function getDocumentVisibility(window) { function getPageInfo() { const { topmostFrame, currentFrameNesting } = getFrameNesting(); return { - location: encodeURIComponent(topmostFrame.location.href), - referrer: encodeURIComponent(topmostFrame.document.referrer) || '0', + location: topmostFrame.location.href, + referrer: + topmostFrame.document.referrer !== '' + ? topmostFrame.document.referrer + : null, masked: currentFrameNesting, wWidth: topmostFrame.innerWidth, wHeight: topmostFrame.innerHeight, @@ -216,7 +228,11 @@ function getPageInfo() { docHidden: getDocumentVisibility(topmostFrame), docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, hLength: history.length, - timing: getTiming() + timing: getTiming(), + version: { + prebid: '$prebid.version$', + adapter: '1.0.0' + } }; } @@ -256,6 +272,7 @@ function setGeneralInfo(bidRequest) { this['auctionId'] = bidRequest.auctionId; this['transactionId'] = bidRequest.transactionId; this['pubId'] = params.pubId; + this['ext'] = params.ext; if (params.pubClick) { this['click'] = params.pubClick; } @@ -326,7 +343,7 @@ function parseSizes(bid) { function getSizes(sizes) { const ret = []; - for (let i = 0, lenght = sizes.length; i < lenght; i++) { + for (let i = 0; i < sizes.length; i++) { const size = sizes[i]; ret.push({width: size[0], height: size[1]}) } diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index b6acd7214a2..d85ae856317 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -62,7 +62,6 @@ export const spec = { const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; const tid = validBidRequests[0].transactionId; const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; - let pubcid = null; let url = bidderRequest.refererInfo.referer; const imp = validBidRequests.map((bid, id) => { @@ -112,10 +111,6 @@ export const spec = { }; }); - if (validBidRequests[0].crumbs && validBidRequests[0].crumbs.pubcid) { - pubcid = validBidRequests[0].crumbs.pubcid; - } - const request = { id: bidderRequest.auctionId, site: { @@ -126,15 +121,26 @@ export const spec = { }, cur, imp, - user: { - buyeruid: pubcid + user: {}, + regs: { + ext: { + gdpr: 0 + } } }; + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + } + return { method: 'POST', url: ENDPOINT_URL, data: JSON.stringify(request), + options: { + contentType: 'application/json' + }, bids: validBidRequests }; }, diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js new file mode 100644 index 00000000000..ce0edb1e19c --- /dev/null +++ b/modules/smaatoBidAdapter.js @@ -0,0 +1,268 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'smaato'; +const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; +const CLIENT = 'prebid_js_$prebid.version$_1.0' + +/** +* Transform BidRequest to OpenRTB-formatted BidRequest Object +* @param {Array} validBidRequests +* @param {any} bidderRequest +* @returns {string} +*/ +const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { + /** + * Turn incoming prebid sizes into openRtb format mapping. + * @param {*} sizes in format [[10, 10], [20, 20]] + * @returns array of openRtb format mappings [{w: 10, h: 10}, {w: 20, h: 20}] + */ + const parseSizes = (sizes) => { + return sizes.map((size) => { + return {w: size[0], h: size[1]}; + }) + } + + const imp = validBidRequests.map(br => { + const bannerMediaType = utils.deepAccess(br, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(br, 'mediaTypes.video'); + let result = { + id: br.bidId, + tagid: utils.deepAccess(br, 'params.adspaceId') + } + + if (bannerMediaType) { + const sizes = parseSizes(utils.getAdUnitSizes(br)); + result.banner = { + w: sizes[0].w, + h: sizes[0].h, + format: sizes + } + } + + if (videoMediaType) { + result.video = { + mimes: videoMediaType.mimes, + minduration: videoMediaType.minduration, + startdelay: videoMediaType.startdelay, + linearity: videoMediaType.linearity, + w: videoMediaType.playerSize[0][0], + h: videoMediaType.playerSize[0][1], + maxduration: videoMediaType.maxduration, + skip: videoMediaType.skip, + protocols: videoMediaType.protocols, + ext: { + rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 + }, + skipmin: videoMediaType.skipmin, + api: videoMediaType.api + } + } + + return result; + }); + + const request = { + id: bidderRequest.auctionId, + at: 1, + imp, + cur: ['USD'], + tmax: bidderRequest.timeout, + site: { + id: window.location.hostname, + publisher: { + id: utils.deepAccess(validBidRequests[0], 'params.publisherId') + }, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer + }, + device: { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, + ext: { + client: CLIENT + } + }; + + Object.assign(request.user, config.getConfig('fpd.user')); + Object.assign(request.site, config.getConfig('fpd.context')); + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent !== undefined) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + utils.logInfo('[SMAATO] OpenRTB Request:', request); + return JSON.stringify(request); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return typeof bid.params === 'object' && + typeof bid.params.publisherId === 'string' && + typeof bid.params.adspaceId === 'string'; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + utils.logInfo('[SMAATO] Client version:', CLIENT); + return { + method: 'POST', + url: validBidRequests[0].params.endpoint || SMAATO_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), + options: { + withCredentials: true, + crossOrigin: true, + } + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, bidRequest) => { + // response is empty (HTTP 204) + if (utils.isEmpty(serverResponse.body)) { + utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids'); + return []; // no bids + } + + let serverResponseHeaders = serverResponse.headers; + const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + + const smtExpires = serverResponseHeaders.get('X-SMT-Expires'); + let ttlSec = 300; + utils.logInfo('[SMAATO] Expires:', smtExpires); + if (smtExpires) { + ttlSec = Math.floor((smtExpires - Date.now()) / 1000); + } + + const res = serverResponse.body; + utils.logInfo('[SMAATO] OpenRTB Response:', res); + + var bids = []; + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + let resultingBid = { + requestId: b.impid, + cpm: b.price || 0, + width: b.w, + height: b.h, + ttl: ttlSec, + creativeId: b.crid, + dealId: b.dealid || null, + netRevenue: true, + currency: res.cur, + meta: { + advertiserDomains: b.adomain, + networkName: b.bidderName, + agencyId: sb.seat + } + }; + + switch (smtAdType) { + case 'Img': + resultingBid.ad = createImgAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Richmedia': + resultingBid.ad = createRichmediaAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Video': + resultingBid.vastXml = b.adm; + resultingBid.meta.mediaType = VIDEO; + bids.push(resultingBid); + break; + default: + utils.logInfo('[SMAATO] Invalid ad type:', smtAdType); + } + }); + }); + + utils.logInfo('[SMAATO] Prebid bids:', bids); + return bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const syncs = [] + return syncs; + } +} +registerBidder(spec); + +const createImgAd = (adm) => { + const image = JSON.parse(adm).image; + + let clickEvent = ''; + image.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
`; + + image.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; + +const createRichmediaAd = (adm) => { + const rich = JSON.parse(adm).richmedia; + let clickEvent = ''; + rich.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
${rich.mediadata.content}`; + + rich.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; diff --git a/modules/smaatoBidAdapter.md b/modules/smaatoBidAdapter.md new file mode 100644 index 00000000000..d26d7ecf64e --- /dev/null +++ b/modules/smaatoBidAdapter.md @@ -0,0 +1,64 @@ +# Overview + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +# Description + +The Smaato adapter requires setup and approval from the Smaato team, even for existing Smaato publishers. Please reach out to your account team or prebid@smaato.com for more information. + +# Test Parameters + +For banner adunits: + +``` +var adUnits = [{ + "code": "banner-unit", + "mediaTypes": { + "banner": { + "sizes": [320, 50] + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + +For video adunits: + +``` +var adUnits = [{ + "code": "video unit", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480], + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 30, + "startdelay": 0, + "linearity": 1, + "protocols": [7], + "skip": 1, + "skipmin": 5, + "api": [7], + "ext": {"rewarded": 0} + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 8f069f551ee..6725f5aff74 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { getAdUnitSizes, logWarn } from '../src/utils.js'; +import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -85,6 +85,11 @@ export const spec = { } }); + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (openRtbBidRequest.imp.length && seatId) { return { method: 'POST', diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 6ead453b622..743cb07b21e 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,8 +2,9 @@ * Adapter to send bids to Undertone */ -import { parseUrl } from '../src/utils.js'; +import { deepAccess, parseUrl } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'undertone'; const URL = 'https://hb.undertone.com/hb'; @@ -73,6 +74,7 @@ function getBannerCoords(id) { export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { bid.params.publisherId = parseInt(bid.params.publisherId); @@ -120,8 +122,20 @@ export const spec = { sizes: bidReq.sizes, params: bidReq.params }; + const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + if (videoMediaType) { + bid.video = { + playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, + streamType: deepAccess(bidReq, 'mediaTypes.video.context') || null, + playbackMethod: deepAccess(bidReq, 'params.video.playbackMethod') || null, + maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, + skippable: deepAccess(bidReq, 'params.video.skippable') || null + }; + bid.mediaType = 'video'; + } payload['x-ut-hb-params'].push(bid); }); + return { method: 'POST', url: reqUrl, @@ -144,9 +158,14 @@ export const spec = { creativeId: bidRes.adId, currency: bidRes.currency, netRevenue: bidRes.netRevenue, - ttl: bidRes.ttl || 360, - ad: bidRes.ad + ttl: bidRes.ttl || 360 }; + if (bidRes.mediaType && bidRes.mediaType === 'video') { + bid.vastXml = bidRes.ad; + bid.mediaType = bidRes.mediaType; + } else { + bid.ad = bidRes.ad + } bids.push(bid); } }); diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9c2b6abf475..b2a9176e342 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -37,7 +37,7 @@ export const spec = { utils._each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) if (bid.params.targeting) { - query.t = createQueryString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting) } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { query.ids = createUserIdString(bid.userIdAsEids) @@ -49,10 +49,16 @@ export const spec = { } }) - if (bidderRequest && bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + query.pubref = bidderRequest.refererInfo.referer + } + + if (bidderRequest.gdprConsent) { + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + if (query.gdpr) { + query.consent = bidderRequest.gdprConsent.consentString + } } } @@ -198,6 +204,23 @@ function createQueryString (obj) { return str.join('&') } +/** + * Creates an unencoded targeting string out of an object with key-values + * @param {Object} obj + * @returns {String} + */ +function createTargetingString (obj) { + let str = [] + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + let key = p + let val = obj[p] + str.push(key + '=' + val) + } + } + return str.join('&') +} + /** * Handles an outstream response after the library is loaded * @param {Object} bid diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js new file mode 100644 index 00000000000..a353665ec33 --- /dev/null +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -0,0 +1,281 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +describe('BoldwinBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'boldwin', + params: { + placementId: 0, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index 2e8d7db92b5..ee4140afa10 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -185,7 +185,10 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { + // from prebid 4425 - "the USP (CCPA) api function __uspapi() always responds synchronously, whether or not privacy data is available, while the GDPR CMP may respond asynchronously + // Because the USP API does not wait for a user response, if it was not successfully obtained before the first auction, we should try again to retrieve privacy data before each subsequent auction. + + it('should not bypass CMP and simply use previously stored consentData', function () { let testConsentData = { uspString: '1YY' }; @@ -208,7 +211,7 @@ describe('consentManagement', function () { let consent = uspDataHandler.getConsentData(); expect(didHookReturn).to.be.true; expect(consent).to.equal(testConsentData.uspString); - sinon.assert.notCalled(uspStub); + sinon.assert.called(uspStub); }); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index f575c04504e..63b04077f4e 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -5,7 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; describe('IndexexchangeAdapter', function () { - const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index dcec1050652..97b45cef00c 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -10,8 +10,10 @@ const { const MOCK = { AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739}, + AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, - BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, + MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}, 'ext': ['asdads']}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, @@ -19,6 +21,15 @@ const MOCK = { BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}] } +function performAuctionWithFloorConfig() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT_WITH_FLOOR); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + function performStandardAuctionWithWinner() { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -28,6 +39,14 @@ function performStandardAuctionWithWinner() { events.emit(BID_WON, MOCK.BID_WON); } +function performMultiFormatAuctionWithNoBid() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function performStandardAuctionWithNoBid() { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -115,6 +134,17 @@ describe('Media.net Analytics Adapter', function() { expect(medianetAnalytics.getlogsQueue().length).to.equal(0); }); + it('should have all applicable sizes in request', function() { + medianetAnalytics.clearlogsQueue(); + performMultiFormatAuctionWithNoBid(); + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + medianetAnalytics.clearlogsQueue(); + + expect(noBidLog.szs).to.equal(encodeURIComponent('300x250|1x1|640x480')); + expect(noBidLog.vplcmtt).to.equal('instream'); + expect(noBidLog.sz2).to.equal(encodeURIComponent('300x250')); + }); + it('should have winner log in standard auction', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); @@ -142,7 +172,24 @@ describe('Media.net Analytics Adapter', function() { ogbdp: '1.1495', flt: '1', supcrid: 'div-gpt-ad-1460505748561-0', - mpvid: '123' + mpvid: '123', + bidflr: '1.1' + }); + }); + + it('should have correct bid floor data in winner log', function() { + medianetAnalytics.clearlogsQueue(); + performAuctionWithFloorConfig(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog[0]).to.include({ + winner: '1', + curr: 'USD', + ogbdp: '1.1495', + bidflr: '1.1', + flrrule: 'banner', + flrdata: encodeURIComponent('ln=||skp=||enfj=true||enfd=||sr=||fs=') }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index a951c74b20b..c1462c3814d 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -132,10 +132,10 @@ describe('onetag', function () { const data = JSON.parse(d); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'onetagSid'); + expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'timing', 'version'); expect(data.location).to.be.a('string'); - expect(data.masked).to.be.a('number'); - expect(data.referrer).to.be.a('string'); + expect(data.masked).to.be.oneOf([0, 1, 2]); + expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); expect(data.sHeight).to.be.a('number'); expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); @@ -148,6 +148,7 @@ describe('onetag', function () { expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); expect(data.bids).to.be.an('array'); + expect(data.version).to.have.all.keys('prebid', 'adapter'); const bids = data['bids']; for (let i = 0; i < bids.length; i++) { const bid = bids[i]; diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index e6f96c92fd9..81af9546ff0 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user'.split(','); + let keys = 'site,device,cur,imp,user,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js new file mode 100644 index 00000000000..95eb36d8a0d --- /dev/null +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -0,0 +1,505 @@ +import { spec } from 'modules/smaatoBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; + +const imageAd = { + image: { + img: { + url: 'https://prebid/static/ad.jpg', + w: 320, + h: 50, + ctaurl: 'https://prebid/track/ctaurl' + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const richmediaAd = { + richmedia: { + mediadata: { + content: '

RICHMEDIA CONTENT

', + w: 800, + h: 600 + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const ADTYPE_IMG = 'Img'; +const ADTYPE_RICHMEDIA = 'Richmedia'; +const ADTYPE_VIDEO = 'Video'; + +const context = { + keywords: 'power tools,drills' +}; + +const user = { + keywords: 'a,b', + gender: 'M', + yob: 1984 +}; + +const openRtbBidResponse = (adType) => { + let adm = ''; + + switch (adType) { + case ADTYPE_IMG: + adm = JSON.stringify(imageAd); + break; + case ADTYPE_RICHMEDIA: + adm = JSON.stringify(richmediaAd); + break; + case ADTYPE_VIDEO: + adm = ''; + break; + default: + throw Error('Invalid AdType'); + } + + let resp = { + body: { + bidid: '04db8629-179d-4bcd-acce-e54722969006', + cur: 'USD', + ext: {}, + id: '5ebea288-f13a-4754-be6d-4ade66c68877', + seatbid: [ + { + bid: [ + { + 'adm': adm, + 'adomain': [ + 'smaato.com' + ], + 'bidderName': 'smaato', + 'cid': 'CM6523', + 'crid': 'CR69381', + 'dealid': '12345', + 'id': '6906aae8-7f74-4edd-9a4f-f49379a3cadd', + 'impid': '226416e6e6bf41', + 'iurl': 'https://prebid/iurl', + 'nurl': 'https://prebid/nurl', + 'price': 0.01, + 'w': 350, + 'h': 50 + } + ], + seat: 'CM6523' + } + ], + }, + headers: { + get: function (header) { + if (header === 'X-SMT-ADTYPE') { + return adType; + } + } + } + }; + return resp; +}; + +const request = { + method: 'POST', + url: 'https://prebid.ad.smaato.net/oapi/prebid', + data: '' +}; + +const interpretedBidsImg = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '
\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsRichmedia = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '

RICHMEDIA CONTENT

\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsVideo = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + vastXml: '', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'video' + } + } +]; + +const defaultBidderRequest = { + gdprConsent: { + consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + refererInfo: { + referer: 'http://example.com/page.html', + }, + timeout: 1200 +}; + +const minimalBidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } +}; + +const singleBannerBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const singleVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + }, + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +describe('smaatoBidAdapterTest', () => { + describe('isBidRequestValid', () => { + it('has valid params', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456'}})).to.be.true; + expect(spec.isBidRequestValid(singleBannerBidRequest)).to.be.true; + }); + it('has invalid params', () => { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123'}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: 456}})).to.be.false; + }); + }); + + describe('buildRequests', () => { + beforeEach(() => { + this.req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); + this.sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + it('can override endpoint', () => { + const overridenEndpoint = 'https://prebid/bidder'; + let bidRequest = utils.deepClone(singleBannerBidRequest); + utils.deepSetValue(bidRequest, 'params.endpoint', overridenEndpoint); + const actualEndpoint = spec.buildRequests([bidRequest], defaultBidderRequest).url; + expect(actualEndpoint).to.equal(overridenEndpoint); + }); + + it('sends correct imps', () => { + expect(this.req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + tagid: 'adspaceId' + } + ]) + }); + + it('sends correct site', () => { + expect(this.req.site.id).to.exist.and.to.be.a('string'); + expect(this.req.site.domain).to.exist.and.to.be.a('string'); + expect(this.req.site.page).to.exist.and.to.be.a('string'); + expect(this.req.site.ref).to.equal('http://example.com/page.html'); + expect(this.req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends gdpr applies if exists', () => { + expect(this.req.regs.ext.gdpr).to.equal(1); + expect(this.req.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + }); + + it('sends no gdpr applies if no gdpr exists', () => { + let req_without_gdpr = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_gdpr.regs.ext.gdpr).to.not.exist; + expect(req_without_gdpr.user.ext.consent).to.not.exist; + }); + + it('sends usp if exists', () => { + expect(this.req.regs.ext.us_privacy).to.equal('uspConsentString'); + }); + + it('sends tmax', () => { + expect(this.req.tmax).to.equal(1200); + }); + + it('sends no usp if no usp exists', () => { + let req_without_usp = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_usp.regs.ext.us_privacy).to.not.exist; + }); + + it('sends fp data', () => { + this.sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context, + user + } + }; + return utils.deepAccess(config, key); + }); + let bidRequest = utils.deepClone(singleBannerBidRequest); + let req_fpd = JSON.parse(spec.buildRequests([bidRequest], defaultBidderRequest).data); + expect(req_fpd.user.gender).to.equal('M'); + expect(req_fpd.user.yob).to.equal(1984); + expect(req_fpd.user.keywords).to.eql('a,b'); + expect(req_fpd.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + expect(req_fpd.site.keywords).to.eql('power tools,drills'); + expect(req_fpd.site.publisher.id).to.equal('publisherId'); + }) + }); + + describe('buildRequests for video imps', () => { + it('sends correct video imps', () => { + let req = JSON.parse(spec.buildRequests([singleVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows combines banner and video imp in single bid request', () => { + let req = JSON.parse(spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows two imps in the same bid request', () => { + let req = JSON.parse(spec.buildRequests([singleBannerBidRequest, singleBannerBidRequest], defaultBidderRequest).data); + expect(req.imp).to.have.length(2); + }); + }); + + describe('interpretResponse', () => { + it('single image reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); + assert.deepStrictEqual(bids, interpretedBidsImg); + }); + it('single richmedia reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_RICHMEDIA), request); + assert.deepStrictEqual(bids, interpretedBidsRichmedia); + }); + it('single video reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_VIDEO), request); + assert.deepStrictEqual(bids, interpretedBidsVideo); + }); + it('ignores bid response with invalid ad type', () => { + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return undefined; + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids).to.be.empty + }); + it('uses correct TTL when expire header exists', () => { + const clock = sinon.useFakeTimers(); + clock.tick(2000); + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return ADTYPE_IMG; + } + if (header === 'X-SMT-Expires') { + return 2000 + (400 * 1000); + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids[0].ttl).to.equal(400); + clock.restore(); + }); + }); +}); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index e15481d47e5..d9f6c9b7256 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -175,6 +175,14 @@ describe('synacormediaBidAdapter ', function () { } }; + let bidderRequestWithCCPA = { + auctionId: 'xyz123', + refererInfo: { + referer: 'https://test.com/foo/bar' + }, + uspConsent: '1YYY' + }; + let expectedDataImp1 = { banner: { format: [ @@ -560,6 +568,18 @@ describe('synacormediaBidAdapter ', function () { } ]); }); + it('should contain the CCPA privacy string when UspConsent is in bidder request', function() { + // banner test + let req = spec.buildRequests([validBidRequest], bidderRequestWithCCPA); + expect(req).be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data).to.exist.and.to.be.an('object'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.regs.ext.us_privacy).to.equal('1YYY'); + expect(req.data.imp).to.eql([expectedDataImp1]); + }) }); describe('Bid Requests with schain object ', function() { diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index e4218019e0d..72321ce415b 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -24,13 +24,49 @@ const invalidBidReq = { auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' }; -const bidReq = [{ +const videoBidReq = [{ adUnitCode: 'div-gpt-ad-1460505748561-0', bidder: BIDDER_CODE, params: { placementId: '10433394', + publisherId: 12345, + video: { + id: 123, + skippable: true, + playbackMethod: 2, + maxDuration: 30 + } + }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}, +{ + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidder: BIDDER_CODE, + params: { + placementId: '10433395', publisherId: 12345 }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; +const bidReq = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345, + }, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' @@ -147,6 +183,20 @@ const bidResArray = [ ttl: 360 } ]; +const bidVideoResponse = [ + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + mediaType: 'video', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; let element; let sandbox; @@ -241,6 +291,23 @@ describe('Undertone Adapter', () => { expect(bid2.publisherId).to.equal(12345); expect(bid2.params).to.be.an('object'); }); + it('should send video fields correctly', function () { + const request = spec.buildRequests(videoBidReq, bidderReq); + const bidVideo = JSON.parse(request.data)['x-ut-hb-params'][0]; + const bidVideo2 = JSON.parse(request.data)['x-ut-hb-params'][1]; + + expect(bidVideo.mediaType).to.equal('video'); + expect(bidVideo.video).to.be.an('object'); + expect(bidVideo.video.playerSize).to.be.an('array'); + expect(bidVideo.video.streamType).to.equal('outstream'); + expect(bidVideo.video.playbackMethod).to.equal(2); + expect(bidVideo.video.maxDuration).to.equal(30); + expect(bidVideo.video.skippable).to.equal(true); + + expect(bidVideo2.video.skippable).to.equal(null); + expect(bidVideo2.video.maxDuration).to.equal(null); + expect(bidVideo2.video.playbackMethod).to.equal(null); + }); it('should send all userIds data to server', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); const bidCommons = JSON.parse(request.data)['commons']; @@ -303,6 +370,13 @@ describe('Undertone Adapter', () => { it('should only use valid bid responses', () => { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); + + it('should detect video response', () => { + const videoResult = spec.interpretResponse({body: bidVideoResponse}); + const vbid = videoResult[0]; + + expect(vbid.mediaType).to.equal('video'); + }); }); describe('getUserSyncs', () => { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 097f85b9b8d..1e20343c1cd 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -10,7 +10,8 @@ const REQUEST = { 'adSize': '728x90', 'targeting': { 'key1': 'value1', - 'key2': 'value2' + 'key2': 'value2', + 'notDoubleEncoded': 'value3,value4' }, 'customParams': { 'extraParam': true, @@ -84,8 +85,8 @@ describe('yieldlabBidAdapter', function () { expect(request.validBidRequests).to.eql([REQUEST]) }) - it('passes targeting to bid request', function () { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2') + it('passes single-encoded targeting to bid request', function () { + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') }) it('passes userids to bid request', function () { @@ -96,6 +97,20 @@ describe('yieldlabBidAdapter', function () { expect(request.url).to.include('extraParam=true&foo=bar') }) + const refererRequest = spec.buildRequests(bidRequests, { + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + referer: 'https://www.yieldlab.de/test?with=querystring', + stack: ['https://www.yieldlab.de/test?with=querystring'] + } + }) + + it('passes encoded referer to bid request', function () { + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') + }) + const gdprRequest = spec.buildRequests(bidRequests, { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA',