diff --git a/gulpfile.js b/gulpfile.js index d2955f7d777..cbebc29e3bf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,7 +39,7 @@ var port = 9999; // Tasks gulp.task('default', ['webpack']); -gulp.task('serve', ['lint', 'build-bundle-dev', 'watch', 'test']); +gulp.task('serve', ['build-bundle-dev', 'watch']); gulp.task('serve-nw', ['lint', 'watch', 'e2etest']); @@ -49,8 +49,8 @@ gulp.task('build', ['build-bundle-prod']); gulp.task('clean', function () { return gulp.src(['build'], { - read: false - }) + read: false + }) .pipe(clean()); }); @@ -78,13 +78,13 @@ var explicitModules = [ function bundle(dev, moduleArr) { var modules = moduleArr || helpers.getArgModules(), - allModules = helpers.getModuleNames(modules); + allModules = helpers.getModuleNames(modules); - if(modules.length === 0) { + if (modules.length === 0) { modules = allModules.filter(module => !explicitModules.includes(module)); } else { var diff = _.difference(modules, allModules); - if(diff.length !== 0) { + if (diff.length !== 0) { throw new gutil.PluginError({ plugin: 'bundle', message: 'invalid modules: ' + diff.join(', ') @@ -106,13 +106,13 @@ function bundle(dev, moduleArr) { gutil.log('Generating bundle:', outputFileName); return gulp.src( - entries - ) + entries + ) .pipe(gulpif(dev, sourcemaps.init({loadMaps: true}))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { - global: prebid.globalVarName - } + global: prebid.globalVarName + } ))) .pipe(gulpif(dev, sourcemaps.write('.'))); } @@ -229,11 +229,11 @@ gulp.task('watch', function () { 'modules/**/*.js', 'test/spec/**/*.js', '!test/spec/loaders/**/*.js' - ], ['lint', 'build-bundle-dev', 'test']); + ], ['build-bundle-dev']); gulp.watch([ 'loaders/**/*.js', 'test/spec/loaders/**/*.js' - ], ['lint']); + ], []); connect.server({ https: argv.https, port: port, @@ -264,7 +264,7 @@ gulp.task('docs', ['clean-docs'], function () { gulp.task('e2etest', ['devpack', 'webpack'], function() { var cmdQueue = []; - if(argv.browserstack) { + if (argv.browserstack) { var browsers = require('./browsers.json'); delete browsers['bs_ie_9_windows_7']; @@ -276,11 +276,11 @@ gulp.task('e2etest', ['devpack', 'webpack'], function() { var startWith = 'bs'; - Object.keys(browsers).filter(function(v){ + Object.keys(browsers).filter(function(v) { return v.substring(0, startWith.length) === startWith && browsers[v].browser !== 'iphone'; - }).map(function(v,i,arr) { - var newArr = (i%2 === 0) ? arr.slice(i,i+2) : null; - if(newArr) { + }).map(function(v, i, arr) { + var newArr = (i % 2 === 0) ? arr.slice(i, i + 2) : null; + if (newArr) { var cmd = 'nightwatch --env ' + newArr.join(',') + cmdStr; cmdQueue.push(cmd); } diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 2ff1d7d0880..5d075b6929c 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -142,7 +142,8 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { reporters: ['mocha'], mochaReporter: { - showDiff: true + showDiff: true, + output: 'minimal' }, // Continuous Integration mode diff --git a/modules/aardvarkBidAdapter.js b/modules/aardvarkBidAdapter.js new file mode 100644 index 00000000000..7d358864b35 --- /dev/null +++ b/modules/aardvarkBidAdapter.js @@ -0,0 +1,159 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'aardvark'; +const DEFAULT_ENDPOINT = 'bidder.rtk.io'; +const SYNC_ENDPOINT = 'sync.rtk.io'; +const AARDVARK_TTL = 300; +const AARDVARK_CURRENCY = 'USD'; + +let hasSynced = false; + +export function resetUserSync() { + hasSynced = false; +} + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return ((typeof bid.params.ai === 'string') && !!bid.params.ai.length && + (typeof bid.params.sc === 'string') && !!bid.params.sc.length); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + var auctionCodes = []; + var requests = []; + var requestsMap = {}; + var referer = utils.getTopWindowUrl(); + var pageCategories = []; + + if (window.top.rtkcategories && Array.isArray(window.top.rtkcategories)) { + pageCategories = window.top.rtkcategories; + } + + utils._each(validBidRequests, function(b) { + var rMap = requestsMap[b.params.ai]; + if (!rMap) { + rMap = { + shortCodes: [], + payload: { + version: 1, + jsonp: false, + rtkreferer: referer + }, + endpoint: DEFAULT_ENDPOINT + }; + + if (pageCategories && pageCategories.length) { + rMap.payload.categories = pageCategories.slice(0); + } + + if (b.params.categories && b.params.categories.length) { + rMap.payload.categories = rMap.payload.categories || [] + utils._each(b.params.categories, function(cat) { + rMap.payload.categories.push(cat); + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + rMap.payload.gdpr = false; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + rMap.payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + if (rMap.payload.gdpr) { + rMap.payload.consent = bidderRequest.gdprConsent.consentString; + } + } + + requestsMap[b.params.ai] = rMap; + auctionCodes.push(b.params.ai); + } + + rMap.shortCodes.push(b.params.sc); + rMap.payload[b.params.sc] = b.bidId; + + if ((typeof b.params.host === 'string') && b.params.host.length && + (b.params.host !== rMap.endpoint)) { + rMap.endpoint = b.params.host; + } + }); + + utils._each(auctionCodes, function(auctionId) { + var req = requestsMap[auctionId]; + requests.push({ + method: 'GET', + url: `//${req.endpoint}/${auctionId}/${req.shortCodes.join('_')}/aardvark`, + data: req.payload, + bidderRequest + }); + }); + + return requests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + var bidResponses = []; + + if (!Array.isArray(serverResponse.body)) { + serverResponse.body = [serverResponse.body]; + } + + utils._each(serverResponse.body, function(rawBid) { + var bidResponse = { + requestId: rawBid.cid, + cpm: rawBid.cpm || 0, + width: rawBid.width || 0, + height: rawBid.height || 0, + currency: rawBid.currency ? rawBid.currency : AARDVARK_CURRENCY, + netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, + ttl: rawBid.ttl ? rawBid.ttl : AARDVARK_TTL, + creativeId: rawBid.creativeId || 0 + }; + + if (rawBid.hasOwnProperty('dealId')) { + bidResponse.dealId = rawBid.dealId + } + + switch (rawBid.media) { + case 'banner': + bidResponse.ad = rawBid.adm + utils.createTrackPixelHtml(decodeURIComponent(rawBid.nurl)); + break; + + default: + return utils.logError('bad Aardvark response (media)', rawBid); + } + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const syncs = []; + var url = '//' + SYNC_ENDPOINT + '/cs'; + var gdprApplies = false; + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + gdprApplies = gdprConsent.gdprApplies; + } + + if (syncOptions.iframeEnabled) { + if (!hasSynced) { + hasSynced = true; + if (gdprApplies) { + url = url + '?g=1&c=' + encodeURIComponent(gdprConsent.consentString); + } + syncs.push({ + type: 'iframe', + url: url + }); + } + } else { + utils.logWarn('Aardvark: Please enable iframe based user sync.'); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/aardvarkBidAdapter.md b/modules/aardvarkBidAdapter.md new file mode 100644 index 00000000000..9f7a128b6f3 --- /dev/null +++ b/modules/aardvarkBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +**Module Name**: Aardvark Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: chris@rtk.io + +# Description + +Module that connects to a RTK.io Ad Units to fetch bids. + +# Test Parameters +``` + var adUnits = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + code: 'div-gpt-ad-1460505748561-0', + + bids: [{ + bidder: 'aardvark', + params: { + ai: '0000', + sc: '1234' + } + }] + + }]; +``` diff --git a/modules/consentManagement.js b/modules/consentManagement.js index c7b6ac4df92..09eb938f314 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -37,8 +37,9 @@ const cmpCallMap = { * based on the appropriate result. * @param {function(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) + * @param {[objects]} adUnits used in the safeframe workflow to know what sizes to include in the $sf.ext.register call */ -function lookupIabConsent(cmpSuccess, cmpError) { +function lookupIabConsent(cmpSuccess, cmpError, adUnits) { let cmpCallbacks; // check if the CMP is located on the same window level as the prebid code. @@ -47,10 +48,37 @@ function lookupIabConsent(cmpSuccess, cmpError) { // in this case, use the IAB's iframe locator sample code (which is slightly cutomized) to try to find the CMP and use postMessage() to communicate with the CMP. if (utils.isFn(window.__cmp)) { window.__cmp('getVendorConsents', null, cmpSuccess); + } else if (inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { + callCmpWhileInSafeFrame(); } else { callCmpWhileInIframe(); } + function inASafeFrame() { + return !!(window.$sf && window.$sf.ext); + } + + function callCmpWhileInSafeFrame() { + function sfCallback(msgName, data) { + if (msgName === 'cmpReturn') { + cmpSuccess(data.vendorConsents); + } + } + + // find sizes from adUnits object + let width = 1; + let height = 1; + + if (Array.isArray(adUnits) && adUnits.length > 0) { + let sizes = utils.getAdUnitSizes(adUnits[0]); + width = sizes[0][0]; + height = sizes[0][1]; + } + + window.$sf.ext.register(width, height, sfCallback); + window.$sf.ext.cmp('getVendorConsents'); + } + function callCmpWhileInIframe() { /** * START OF STOCK CODE FROM IAB 1.1 CMP SPEC @@ -134,6 +162,7 @@ export function requestBidsHook(config, fn) { args = arguments; nextFn = fn; haveExited = false; + let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; // in case we already have consent (eg during bid refresh) if (consentData) { @@ -145,7 +174,7 @@ export function requestBidsHook(config, fn) { return nextFn.apply(context, args); } - cmpCallMap[userCMP].call(this, processCmpData, cmpFailed); + cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, adUnits); // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished) if (!haveExited) { @@ -245,6 +274,7 @@ function exitModule(errMsg) { */ export function resetConsentData() { consentData = undefined; + gdprDataHandler.setConsentData(null); } /** diff --git a/modules/gxoneBidAdapter.js b/modules/gxoneBidAdapter.js new file mode 100644 index 00000000000..77c5ae2b1b7 --- /dev/null +++ b/modules/gxoneBidAdapter.js @@ -0,0 +1,141 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'gxone'; +const ENDPOINT_URL = '//ads.gx1as.com/hb'; +const TIME_TO_LIVE = 360; +const ADAPTER_SYNC_URL = '//ads.gx1as.com/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +/** + * GXOne Bid Adapter. + * Contact: olivier@geronimo.co + * + */ +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + reqId = bid.bidderRequestId; + }); + + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + r: reqId, + }; + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap, + }; + }, + + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; + } + } +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/gxoneBidAdapter.md b/modules/gxoneBidAdapter.md new file mode 100755 index 00000000000..3168d297da3 --- /dev/null +++ b/modules/gxoneBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: GXOne Bidder Adapter +Module Type: Bidder Adapter +Maintainer: olivier@geronimo.co + +# Description + +Module that connects to GXOne demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "gxone", + params: { + uid: '2', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "gxone", + params: { + uid: 9, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/lifestreetBidAdapter.js b/modules/lifestreetBidAdapter.js index 919e83576d3..17aeeb56f2e 100644 --- a/modules/lifestreetBidAdapter.js +++ b/modules/lifestreetBidAdapter.js @@ -12,7 +12,7 @@ const urlTemplate = template`//ads.lfstmedia.com/gate/${'adapter'}/${'slot'}?adk * * @param {BidRequest} bid The bid params to use for formatting a request */ -function formatBidRequest(bid) { +function formatBidRequest(bid, bidderRequest) { let url = urlTemplate({ adapter: 'prebid', slot: bid.params.slot, @@ -28,6 +28,16 @@ function formatBidRequest(bid) { hbver: ADAPTER_VERSION }); + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + const gdpr = '&__gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0'); + url += gdpr; + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + url += '&__consent=' + bidderRequest.gdprConsent.consentString; + } + } + return { method: 'GET', url: url, @@ -95,9 +105,9 @@ export const spec = { * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests) { + buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { - return formatBidRequest(bid) + return formatBidRequest(bid, bidderRequest) }); }, diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 8fe09ab74e6..08232231417 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -71,11 +71,16 @@ function getSize(size) { } } -function configuredParams(params) { - return { +function extParams(params, gdpr) { + let ext = { customer_id: params.cid, prebid_version: $$PREBID_GLOBAL$$.version + }; + ext.gdpr_applies = !!(gdpr && gdpr.gdprApplies); + if (ext.gdpr_applies) { + ext.gdpr_consent_string = gdpr.consentString || ''; } + return ext; } function slotParams(bidRequest) { @@ -100,13 +105,13 @@ function slotParams(bidRequest) { return params; } -function generatePayload(bidRequests, timeout) { +function generatePayload(bidRequests, bidderRequests) { return { site: siteDetails(bidRequests[0].params.site), - ext: configuredParams(bidRequests[0].params), + ext: extParams(bidRequests[0].params, bidderRequests.gdprConsent), id: bidRequests[0].auctionId, imp: bidRequests.map(request => slotParams(request)), - tmax: timeout + tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') } } @@ -153,12 +158,11 @@ 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 {BidderRequests} bidderRequests * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bidRequests, auctionData) { - let timeout = auctionData.timeout || config.getConfig('bidderTimeout'); - let payload = generatePayload(bidRequests, timeout); - + buildRequests: function(bidRequests, bidderRequests) { + let payload = generatePayload(bidRequests, bidderRequests); return { method: 'POST', url: BID_URL, diff --git a/modules/playgroundxyzBidAdapter.js b/modules/playgroundxyzBidAdapter.js new file mode 100644 index 00000000000..527ecf00037 --- /dev/null +++ b/modules/playgroundxyzBidAdapter.js @@ -0,0 +1,374 @@ +import { Renderer } from 'src/Renderer'; +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER, VIDEO } from 'src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; + +const BIDDER_CODE = 'playgroundxyz'; +const URL = 'https://ads.playground.xyz/host-config/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playback_method', 'frameworks']; +const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; +const SOURCE = 'pbjs'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['playgroundxyz'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + }, + + /** + * 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. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj; + if (userObjBid) { + userObj = {}; + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach(param => userObj[param] = userObjBid.params.user[param]); + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + } + }; + if (member > 0) { + payload.member_id = member; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + // sort tags by cpm + serverResponse.tags.sort(function (x, y) { + return y.cpm - x.cpm; + }); + + serverResponse.tags.forEach((serverBid, index) => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + // set cpm to 0 if it's not the first one + bid.cpm = index === 0 ? bid.cpm : 0; + bids.push(bid); + } + } + }); + } + return bids; + }, + + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + }]; + } + } +} + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('PlaygroundXYZ outstream video impression event'), + loaded: () => utils.logMessage('PlaygroundXYZ outstream video loaded event'), + ended: () => { + utils.logMessage('PlaygroundXYZ outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/* Turn keywords parameter into ut-compatible format */ +function getKeywords(keywords) { + let arrs = []; + + utils._each(keywords, (v, k) => { + if (utils.isArray(v)) { + let values = []; + utils._each(v, (val) => { + val = utils.getValueString('keywords.' + k, val); + if (val) { values.push(val); } + }); + v = values; + } else { + v = utils.getValueString('keywords.' + k, v); + if (utils.isStr(v)) { + v = [v]; + } else { + return; + } // unsuported types - don't send a key + } + arrs.push({ key: k, value: v }); + }); + + return arrs; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + playgroundxyz: { + buyerMemberId: rtbBid.buyer_member_id + } + }; + + if (rtbBid.rtb.video) { + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastUrl: rtbBid.rtb.video.asset_url, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + // This supports Outstream Video + if (rtbBid.renderer_url) { + const rendererOptions = utils.deepAccess( + bidderRequest.bids[0], + 'renderer.options' + ); + + Object.assign(bid, { + adResponse: serverBid, + renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) + }); + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!utils.isEmpty(bid.params.keywords)) { + tag.keywords = getKeywords(bid.params.keywords); + } + + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + + if ( + (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) + ) { + tag.ad_types.push(BANNER); + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function outstreamRender(bid) { + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else { + return BANNER; + } +} + +registerBidder(spec); diff --git a/modules/playgroundxyzBidAdapter.md b/modules/playgroundxyzBidAdapter.md new file mode 100644 index 00000000000..1f675167b4a --- /dev/null +++ b/modules/playgroundxyzBidAdapter.md @@ -0,0 +1,103 @@ +# Overview + +``` +Module Name: Appnexus Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@prebid.org +``` + +# Description + +Connects to playgroundxyz ad server for bids. + +Appnexus bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'playgroundxyz', + params: { + placementId: '10433394' + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[300, 250], [300,600]], + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + brand: { + required: true + }, + image: { + required: true + }, + clickUrl: { + required: true + }, + } + }, + bids: [{ + bidder: 'playgroundxyz', + params: { + placementId: '9880618' + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream' + }, + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: '9333431', + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '5768085', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + } + ] + } +]; +``` diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index f499f5a0ae4..58a95ea366e 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -37,7 +37,7 @@ const availVendorDefaults = { adapter: 'prebidServer', cookieSet: false, enabled: true, - endpoint: '//prebid.adnxs.com/pbs/v1/auction', + endpoint: '//prebid.adnxs.com/pbs/v1/openrtb2/auction', syncEndpoint: '//prebid.adnxs.com/pbs/v1/cookie_sync', timeout: 1000 }, @@ -96,36 +96,56 @@ function setS2sConfig(options) { } _s2sConfig = options; - if (options.syncEndpoint) { - queueSync(options.bidders); - } } getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); +/** + * resets the _synced variable back to false, primiarily used for testing purposes +*/ +export function resetSyncedStatus() { + _synced = false; +} + /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes) { +function queueSync(bidderCodes, gdprConsent) { if (_synced) { return; } _synced = true; - const payload = JSON.stringify({ + + const payload = { uuid: utils.generateUUID(), bidders: bidderCodes - }); - ajax(_s2sConfig.syncEndpoint, (response) => { - try { - response = JSON.parse(response); - response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder)); - } catch (e) { - utils.logError(e); + }; + + if (gdprConsent) { + // only populate gdpr field if we know CMP returned consent information (ie didn't timeout or have an error) + if (gdprConsent.consentString) { + payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; } - }, - payload, { - contentType: 'text/plain', - withCredentials: true - }); + // attempt to populate gdpr_consent if we know gdprApplies or it may apply + if (gdprConsent.gdprApplies !== false) { + payload.gdpr_consent = gdprConsent.consentString; + } + } + const jsonPayload = JSON.stringify(payload); + + ajax(_s2sConfig.syncEndpoint, + (response) => { + try { + response = JSON.parse(response); + response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder)); + } catch (e) { + utils.logError(e); + } + }, + jsonPayload, + { + contentType: 'text/plain', + withCredentials: true + }); } /** @@ -348,9 +368,6 @@ const LEGACY_PROTOCOL = { if (result.status === 'OK' || result.status === 'no_cookie') { if (result.bidder_status) { result.bidder_status.forEach(bidder => { - if (bidder.no_cookie) { - doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder); - } if (bidder.error) { utils.logWarn(`Prebid Server returned error: '${bidder.error}' for ${bidder.bidder}`); } @@ -637,7 +654,7 @@ const OPEN_RTB_PROTOCOL = { * const bids = protocol().interpretResponse(response, bidRequests, requestedBidders); */ const protocolAdapter = () => { - const OPEN_RTB_PATH = 'openrtb2/auction'; + const OPEN_RTB_PATH = '/openrtb2/'; const endpoint = (_s2sConfig && _s2sConfig.endpoint) || ''; const isOpenRtb = ~endpoint.indexOf(OPEN_RTB_PATH); @@ -666,6 +683,11 @@ export function PrebidServer() { .reduce(utils.flatten) .filter(utils.uniques); + if (_s2sConfig && _s2sConfig.syncEndpoint) { + let consent = (Array.isArray(bidRequests) && bidRequests.length > 0) ? bidRequests[0].gdprConsent : undefined; + queueSync(_s2sConfig.bidders, consent); + } + const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, adUnitsWithSizes); const requestJson = JSON.stringify(request); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 14da2c45164..989ce180463 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -268,27 +268,32 @@ export const spec = { interpretResponse: (response, request) => { const bidResponses = []; try { - if (response.body && response.body.seatbid && response.body.seatbid[0] && response.body.seatbid[0].bid) { - response.body.seatbid[0].bid.forEach(bid => { - let newBid = { - requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid || bid.id, - dealId: bid.dealid, - currency: CURRENCY, - netRevenue: NET_REVENUE, - ttl: 300, - referrer: utils.getTopWindowUrl(), - ad: bid.adm - }; - - if (bid.ext && bid.ext.deal_channel) { - newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; - } - - bidResponses.push(newBid); + if (response.body && response.body.seatbid && utils.isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + utils.isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: 300, + referrer: utils.getTopWindowUrl(), + ad: bid.adm + }; + + if (bid.ext && bid.ext.deal_channel) { + newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; + } + + bidResponses.push(newBid); + }); }); } } catch (error) { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index d6a69fda625..061440e7c0e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -37,6 +37,7 @@ var sizeMap = { 43: '320x50', 44: '300x50', 48: '300x300', + 53: '1024x768', 54: '300x1050', 55: '970x90', 57: '970x250', @@ -232,8 +233,8 @@ export const spec = { 'p_screen_res', _getScreenResolution(), 'kw', keywords, 'tk_user_key', userId, - 'p_geo.latitude', parseFloat(latitude).toFixed(4), - 'p_geo.longitude', parseFloat(longitude).toFixed(4) + 'p_geo.latitude', isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), + 'p_geo.longitude', isNaN(parseFloat(longitude)) ? undefined : parseFloat(longitude).toFixed(4) ]; if (gdprConsent) { @@ -357,12 +358,24 @@ export const spec = { return bids; }, []); }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function (syncOptions, responses, gdprConsent) { if (!hasSynced && syncOptions.iframeEnabled) { + // data is only assigned if params are available to pass to SYNC_ENDPOINT + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + hasSynced = true; return { type: 'iframe', - url: SYNC_ENDPOINT + url: SYNC_ENDPOINT + params }; } } diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 7db4747927a..0767a51e545 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -22,9 +22,10 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - bidder request object * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (validBidRequests) { + buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info // if your bidder supports multiple currencies, use config.getConfig(currency) @@ -53,6 +54,12 @@ export const spec = { bidId: bid.bidId, prebidVersion: '$prebid.version$' }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + var payloadString = JSON.stringify(payload); return { method: 'POST', diff --git a/package.json b/package.json index 28cb310599e..7e036004973 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.11.0-pre", + "version": "1.12.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/auction.js b/src/auction.js index 8a23605bf0e..654b6dbb189 100644 --- a/src/auction.js +++ b/src/auction.js @@ -298,8 +298,8 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) { events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); // a publisher-defined renderer can be used to render bids - const adUnitRenderer = - bidRequest.bids && bidRequest.bids[0] && bidRequest.bids[0].renderer; + const bidReq = bidRequest.bids && find(bidRequest.bids, bid => bid.adUnitCode == adUnitCode); + const adUnitRenderer = bidReq && bidReq.renderer; if (adUnitRenderer && adUnitRenderer.url) { bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); diff --git a/src/utils.js b/src/utils.js index cf977124dd1..c595101bae1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -107,6 +107,35 @@ exports.transformAdServerTargetingObj = function (targeting) { } }; +/** + * Read an adUnit object and return the sizes used in an [[728, 90]] format (even if they had [728, 90] defined) + * Preference is given to the `adUnit.mediaTypes.banner.sizes` object over the `adUnit.sizes` + * @param {object} adUnit one adUnit object from the normal list of adUnits + * @returns {array[array[number]]} array of arrays containing numeric sizes + */ +export function getAdUnitSizes(adUnit) { + if (!adUnit) { + return; + } + + let sizes = []; + if (adUnit.mediaTypes && adUnit.mediaTypes.banner && Array.isArray(adUnit.mediaTypes.banner.sizes)) { + let bannerSizes = adUnit.mediaTypes.banner.sizes; + if (Array.isArray(bannerSizes[0])) { + sizes = bannerSizes; + } else { + sizes.push(bannerSizes); + } + } else if (Array.isArray(adUnit.sizes)) { + if (Array.isArray(adUnit.sizes[0])) { + sizes = adUnit.sizes; + } else { + sizes.push(adUnit.sizes); + } + } + return sizes; +} + /** * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index ef50d2b6294..6fbc48b3cdc 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -7,6 +7,7 @@ import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; import { config } from 'src/config'; import * as store from 'src/videoCache'; import * as ajaxLib from 'src/ajax'; +import find from 'core-js/library/fn/array/find'; const adloader = require('../../src/adloader'); var assert = require('assert'); @@ -643,6 +644,35 @@ describe('auctionmanager.js', function () { const addedBid = auction.getBidsReceived().pop(); assert.equal(addedBid.renderer.url, 'renderer.js'); }); + + it('bid for a regular unit and a video unit', function() { + let renderer = { + url: 'renderer.js', + render: (bid) => bid + }; + + // make sure that if the renderer is only on the second ad unit, prebid + // still correctly uses it + let bid = mockBid(); + let bidRequests = [mockBidRequest(bid)]; + + bidRequests[0].bids[1] = Object.assign({ + renderer, + bidId: utils.getUniqueIdentifierStr() + }, bidRequests[0].bids[0]); + bidRequests[0].bids[0].adUnitCode = ADUNIT_CODE1; + + makeRequestsStub.returns(bidRequests); + + // this should correspond with the second bid in the bidReq because of the ad unit code + bid.mediaType = 'video-outstream'; + spec.interpretResponse.returns(bid); + + auction.callBids(); + + const addedBid = find(auction.getBidsReceived(), bid => bid.adUnitCode == ADUNIT_CODE); + assert.equal(addedBid.renderer.url, 'renderer.js'); + }); }); describe('when auction timeout is 20', () => { diff --git a/test/spec/modules/aardvarkBidAdapter_spec.js b/test/spec/modules/aardvarkBidAdapter_spec.js new file mode 100644 index 00000000000..d2b9cbc0fa8 --- /dev/null +++ b/test/spec/modules/aardvarkBidAdapter_spec.js @@ -0,0 +1,264 @@ +import { expect } from 'chai'; +import { spec } from 'modules/aardvarkBidAdapter'; + +describe('aardvarkAdapterTest', () => { + describe('forming valid bidRequests', () => { + it('should accept valid bidRequests', () => { + expect(spec.isBidRequestValid({ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + sizes: [[300, 250]] + })).to.equal(true); + }); + + it('should reject invalid bidRequests', () => { + expect(spec.isBidRequestValid({ + bidder: 'aardvark', + params: { + ai: 'xiby', + }, + sizes: [[300, 250]] + })).to.equal(false); + }); + }); + + describe('executing network requests', () => { + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'RAZd', + host: 'adzone.pub.com' + }, + adUnitCode: 'bbb', + transactionId: '193995b4-7122-4739-959b-2463282a138b', + sizes: [[800, 600]], + bidId: '22aidtbx5eabd9', + bidderRequestId: '70deaff71c281d', + auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; + + it('should use HTTP GET method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('should call the correct bidRequest url', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.match(new RegExp('^\/\/adzone.pub.com/xiby/TdAx_RAZd/aardvark\?')); + }); + + it('should have correct data', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(1); + expect(requests[0].data.version).to.equal(1); + expect(requests[0].data.jsonp).to.equal(false); + expect(requests[0].data.TdAx).to.equal('1abgs362e0x48a8'); + expect(requests[0].data.rtkreferer).to.not.be.undefined; + expect(requests[0].data.RAZd).to.equal('22aidtbx5eabd9'); + }); + }); + + describe('splitting multi-auction ad units into own requests', () => { + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'Toby', + sc: 'TdAx', + categories: ['cat1', 'cat2'] + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'RAZd', + host: 'adzone.pub.com' + }, + adUnitCode: 'bbb', + transactionId: '193995b4-7122-4739-959b-2463282a138b', + sizes: [[800, 600]], + bidId: '22aidtbx5eabd9', + bidderRequestId: '70deaff71c281d', + auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; + + it('should use HTTP GET method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('should call the correct bidRequest urls for each auction', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].url).to.match(new RegExp('^\/\/bidder.rtk.io/Toby/TdAx/aardvark\?')); + expect(requests[0].data.categories.length).to.equal(2); + expect(requests[1].url).to.match(new RegExp('^\/\/adzone.pub.com/xiby/RAZd/aardvark\?')); + }); + + it('should have correct data', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(2); + expect(requests[0].data.version).to.equal(1); + expect(requests[0].data.jsonp).to.equal(false); + expect(requests[0].data.TdAx).to.equal('1abgs362e0x48a8'); + expect(requests[0].data.rtkreferer).to.not.be.undefined; + expect(requests[0].data.RAZd).to.be.undefined; + expect(requests[1].data.version).to.equal(1); + expect(requests[1].data.jsonp).to.equal(false); + expect(requests[1].data.TdAx).to.be.undefined; + expect(requests[1].data.rtkreferer).to.not.be.undefined; + expect(requests[1].data.RAZd).to.equal('22aidtbx5eabd9'); + }); + }); + + describe('GDPR conformity', () => { + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + }]; + + const bidderRequest = { + gdprConsent: { + consentString: 'awefasdfwefasdfasd', + gdprApplies: true + } + }; + + it('should transmit correct data', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].data.gdpr).to.equal(true); + expect(requests[0].data.consent).to.equal('awefasdfwefasdfasd'); + }); + }); + + describe('GDPR absence conformity', () => { + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + }, + adUnitCode: 'aaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + }]; + + const bidderRequest = { + gdprConsent: undefined + }; + + it('should transmit correct data', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].data.gdpr).to.be.undefined; + expect(requests[0].data.consent).to.be.undefined; + }); + }); + + describe('interpretResponse', () => { + it('should handle bid responses', () => { + const serverResponse = { + body: [ + { + media: 'banner', + nurl: 'http://www.nurl.com/0', + cpm: 0.09, + width: 300, + height: 250, + cid: '22aidtbx5eabd9', + adm: '', + dealId: 'dealing', + ttl: 200, + }, + { + media: 'banner', + nurl: 'http://www.nurl.com/1', + cpm: 0.19, + width: 300, + height: 250, + cid: '1abgs362e0x48a8', + adm: '', + ttl: 200, + } + ], + headers: {} + }; + + const result = spec.interpretResponse(serverResponse, {}); + expect(result.length).to.equal(2); + + expect(result[0].requestId).to.equal('22aidtbx5eabd9'); + expect(result[0].cpm).to.equal(0.09); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(200); + expect(result[0].dealId).to.equal('dealing'); + expect(result[0].ad).to.not.be.undefined; + + expect(result[1].requestId).to.equal('1abgs362e0x48a8'); + expect(result[1].cpm).to.equal(0.19); + expect(result[1].width).to.equal(300); + expect(result[1].height).to.equal(250); + expect(result[1].currency).to.equal('USD'); + expect(result[1].ttl).to.equal(200); + expect(result[1].ad).to.not.be.undefined; + }); + + it('should handle nobid responses', () => { + var emptyResponse = [{ + nurl: '', + cid: '9e5a09319e18f1', + media: 'banner', + error: 'No bids received for 9DgF', + adm: '', + id: '9DgF', + cpm: 0.00 + }]; + + var result = spec.interpretResponse({ body: emptyResponse }, {}); + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0.0); + }); + }); +}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 5974ac79324..a837a0cce8a 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -77,7 +77,7 @@ describe('consentManagement', function () { utils.logWarn.restore(); config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); - gdprDataHandler.consentData = null; + resetConsentData(); }); it('should return Warning message and return to hooked function', () => { @@ -113,7 +113,7 @@ describe('consentManagement', function () { $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); cmpStub.restore(); delete window.__cmp; - gdprDataHandler.consentData = null; + resetConsentData(); }); it('should bypass CMP and simply use previously stored consentData', () => { @@ -146,13 +146,66 @@ describe('consentManagement', function () { }); }); + describe('CMP workflow for safeframe page', () => { + let registerStub = sinon.stub(); + + beforeEach(() => { + didHookReturn = false; + window.$sf = { + ext: { + register: function() {}, + cmp: function() {} + } + }; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(() => { + delete window.$sf; + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + registerStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + resetConsentData(); + }); + + it('should return the consent data from a safeframe callback', () => { + var testConsentData = { + data: { + msgName: 'cmpReturn', + vendorConsents: { + metadata: 'abc123def', + gdprApplies: true + } + } + }; + registerStub = sinon.stub(window.$sf.ext, 'register').callsFake((...args) => { + args[2](testConsentData.data.msgName, testConsentData.data); + }); + + setConfig(goodConfigWithAllowAuction); + debugger; //eslint-disable-line + requestBidsHook({adUnits: [{ sizes: [[300, 250]] }]}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal('abc123def'); + expect(consent.gdprApplies).to.be.true; + }); + }); + describe('CMP workflow for iframed page', () => { let eventStub = sinon.stub(); let cmpStub = sinon.stub(); beforeEach(() => { didHookReturn = false; - resetConsentData(); window.__cmp = function() {}; sinon.stub(utils, 'logError'); sinon.stub(utils, 'logWarn'); @@ -166,7 +219,7 @@ describe('consentManagement', function () { delete window.__cmp; utils.logError.restore(); utils.logWarn.restore(); - gdprDataHandler.consentData = null; + resetConsentData(); }); it('should return the consent string from a postmessage + addEventListener response', () => { @@ -210,7 +263,6 @@ describe('consentManagement', function () { beforeEach(() => { didHookReturn = false; - resetConsentData(); sinon.stub(utils, 'logError'); sinon.stub(utils, 'logWarn'); window.__cmp = function() {}; @@ -223,7 +275,7 @@ describe('consentManagement', function () { utils.logError.restore(); utils.logWarn.restore(); delete window.__cmp; - gdprDataHandler.consentData = null; + resetConsentData(); }); it('performs lookup check and stores consentData for a valid existing user', () => { diff --git a/test/spec/modules/gxoneBidAdapter_spec.js b/test/spec/modules/gxoneBidAdapter_spec.js new file mode 100644 index 00000000000..f34f4358490 --- /dev/null +++ b/test/spec/modules/gxoneBidAdapter_spec.js @@ -0,0 +1,293 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gxoneBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('GXOne Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gxone', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + let bidRequests = [ + { + 'bidder': 'gxone', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 4, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 5, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 6, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'gxone', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', () => { + const bidRequests = [ + { + 'bidder': 'gxone', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 5, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'gxone', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '7' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'gxone', + 'params': { + 'uid': '8' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/lifestreetBidAdapter_spec.js b/test/spec/modules/lifestreetBidAdapter_spec.js index b47c5f949e2..2c48a0f1892 100644 --- a/test/spec/modules/lifestreetBidAdapter_spec.js +++ b/test/spec/modules/lifestreetBidAdapter_spec.js @@ -109,10 +109,51 @@ describe('LifestreetAdapter', () => { it('should include gzip', () => { expect(request.url).to.contain('__gz=1'); }); + it('should not contain __gdpr parameter', () => { + expect(request.url).to.not.contain('__gdpr'); + }); + it('should not contain __concent parameter', () => { + expect(request.url).to.not.contain('__consent'); + }); it('should contain the right version of adapter', () => { expect(request.url).to.contain('__hbver=' + ADAPTER_VERSION); }); + + it('should contain __gdpr and __consent parameters', () => { + const options = { + gdprConsent: { + gdprApplies: true, + consentString: 'test', + vendorData: {} + } + }; + let [request] = spec.buildRequests(bidRequest.bids, options); + expect(request.url).to.contain('__gdpr=1'); + expect(request.url).to.contain('__consent=test'); + }); + it('should contain __gdpr parameters', () => { + const options = { + gdprConsent: { + gdprApplies: true, + vendorData: {} + } + }; + let [request] = spec.buildRequests(bidRequest.bids, options); + expect(request.url).to.contain('__gdpr=1'); + expect(request.url).to.not.contain('__consent'); + }); + it('should contain __consent parameters', () => { + const options = { + gdprConsent: { + consentString: 'test', + vendorData: {} + } + }; + let [request] = spec.buildRequests(bidRequest.bids, options); + expect(request.url).to.not.contain('__gdpr'); + expect(request.url).to.contain('__consent=test'); + }); }); describe('interpretResponse()', () => { it('should return formatted bid response with required properties', () => { diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 520ec34fc7d..a10dcb2624d 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -80,7 +80,8 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ @@ -129,7 +130,8 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ @@ -331,7 +333,64 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' - }]; + }], + VALID_BIDDER_REQUEST_WITH_GDPR = { + 'gdprConsent': { + 'consentString': 'consentString', + 'gdprApplies': true, + }, + 'timeout': 3000, + }, + VALID_PAYLOAD_FOR_GDPR = { + 'site': { + 'domain': 'media.net', + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_consent_string': 'consentString', + 'gdpr_applies': true, + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0' + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123' + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }], + 'tmax': 3000, + }; describe('Media.net bid adapter', () => { describe('isBidRequestValid', () => { @@ -371,6 +430,12 @@ describe('Media.net bid adapter', () => { let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR, VALID_AUCTIONDATA); expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_INVALID_BIDFLOOR); }); + + it('should add gdpr to response ext', () => { + let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_BIDDER_REQUEST_WITH_GDPR); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_FOR_GDPR); + }); + describe('build requests: when page meta-data is available', () => { it('should pass canonical, twitter and fb paramters if available', () => { let sandbox = sinon.sandbox.create(); diff --git a/test/spec/modules/playgroundxyzBidAdapter_spec.js b/test/spec/modules/playgroundxyzBidAdapter_spec.js new file mode 100644 index 00000000000..30034b5261a --- /dev/null +++ b/test/spec/modules/playgroundxyzBidAdapter_spec.js @@ -0,0 +1,410 @@ +import { expect } from 'chai'; +import { spec } from 'modules/playgroundxyzBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; + +const ENDPOINT = 'https://ads.playground.xyz/host-config/prebid'; + +describe('playgroundxyzBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'playgroundxyz', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'playground', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should parse out private sizes', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); + }); + + it('should add source and verison to the tag', () => { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', () => { + ['banner', 'video'].forEach(type => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + }); + }); + + it('should populate the ad_types array on outstream requests', () => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + }); + + it('sends bid request to ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should attach valid video params to the tag', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + }); + + it('should attach valid user params to the tag', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + external_uid: '123', + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + }); + }); + + it('should convert keyword params to proper form and attaches to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }]); + }); + + it('should add payment rules to the request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + + it('should add gdpr consent information to the request', () => { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'playground', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + }); + }) + + describe('interpretResponse', () => { + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 333333, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.3, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'http://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + }, + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'http://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'playgroundxyz': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles non-banner media responses', () => { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + } + }] + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('supports configuring outstream renderers', () => { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + renderer: { + options: { + adText: 'configured' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + }); +}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index cdb3113c205..9c25e0439b3 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { PrebidServer as Adapter } from 'modules/prebidServerBidAdapter'; +import { PrebidServer as Adapter, resetSyncedStatus } from 'modules/prebidServerBidAdapter'; import adapterManager from 'src/adaptermanager'; import * as utils from 'src/utils'; import cookie from 'src/cookie'; @@ -375,6 +375,7 @@ describe('S2S Adapter', () => { requests = []; xhr.onCreate = request => requests.push(request); config.resetConfig(); + resetSyncedStatus(); }); afterEach(() => xhr.restore()); @@ -392,11 +393,11 @@ describe('S2S Adapter', () => { expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string'); }); - it('adds gdpr consent information to ortb2 request depending on module use', () => { + it('adds gdpr consent information to ortb2 request depending on presence of module', () => { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - let consentConfig = { consentManagement: { cmp: 'iab' }, s2sConfig: ortb2Config }; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -424,6 +425,67 @@ describe('S2S Adapter', () => { $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); }); + it('check gdpr info gets added into cookie_sync request: have consent data', () => { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + + gdprBidRequest[0].gdprConsent = { + consentString: 'abc123def', + gdprApplies: true + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.gdpr).is.equal(1); + expect(requestBid.gdpr_consent).is.equal('abc123def'); + }); + + it('check gdpr info gets added into cookie_sync request: have consent data but gdprApplies is false', () => { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: 'xyz789abcc', + gdprApplies: false + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.gdpr).is.equal(0); + expect(requestBid.gdpr_consent).is.undefined; + }); + + it('checks gdpr info gets added to cookie_sync request: consent data unknown', () => { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: undefined, + gdprApplies: undefined + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.gdpr).is.undefined; + expect(requestBid.gdpr_consent).is.undefined; + }); + it('sets invalid cacheMarkup value to 0', () => { const s2sConfig = Object.assign({}, CONFIG, { cacheMarkup: 999 @@ -794,31 +856,6 @@ describe('S2S Adapter', () => { expect(bid_request_passed).to.have.property('adId', '123'); }); - it('does cookie sync when no_cookie response', () => { - server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - - config.setConfig({s2sConfig: CONFIG}); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); - - sinon.assert.calledOnce(utils.triggerPixel); - sinon.assert.calledWith(utils.triggerPixel, 'https://pixel.rubiconproject.com/exchange/sync.php?p=prebid'); - sinon.assert.calledOnce(utils.insertUserSyncIframe); - sinon.assert.calledWith(utils.insertUserSyncIframe, '//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dpubmatic%26uid%3D'); - }); - - it('logs error when no_cookie response is missing type or url', () => { - server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE_ERROR)); - - config.setConfig({s2sConfig: CONFIG}); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); - - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(utils.insertUserSyncIframe); - sinon.assert.calledTwice(utils.logError); - }); - it('does not call cookieSet cookie sync when no_cookie response && not opted in', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); @@ -982,7 +1019,7 @@ describe('S2S Adapter', () => { expect(vendorConfig.cookieSet).to.be.false; expect(vendorConfig.cookieSetUrl).to.be.undefined; expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', '//prebid.adnxs.com/pbs/v1/auction'); + expect(vendorConfig).to.have.property('endpoint', '//prebid.adnxs.com/pbs/v1/openrtb2/auction'); expect(vendorConfig).to.have.property('syncEndpoint', '//prebid.adnxs.com/pbs/v1/cookie_sync'); expect(vendorConfig).to.have.property('timeout', 750); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 7ea10315a4e..09acbbe95a0 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -49,6 +49,18 @@ describe('PubMatic adapter', () => { 'deal_channel': 6 } }] + }, { + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315BEF', + 'impid': '22bddb28db77e', + 'price': 1.7, + 'adm': 'image3.pubmatic.com Layer based creative', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 5 + } + }] }] } }; @@ -213,6 +225,22 @@ describe('PubMatic adapter', () => { expect(response[0].ttl).to.equal(300); expect(response[0].referrer).to.include(utils.getTopWindowUrl()); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + + expect(response[1].requestId).to.equal(bidResponses.body.seatbid[1].bid[0].impid); + expect(response[1].cpm).to.equal((bidResponses.body.seatbid[1].bid[0].price).toFixed(2)); + expect(response[1].width).to.equal(bidResponses.body.seatbid[1].bid[0].w); + expect(response[1].height).to.equal(bidResponses.body.seatbid[1].bid[0].h); + if (bidResponses.body.seatbid[1].bid[0].crid) { + expect(response[1].creativeId).to.equal(bidResponses.body.seatbid[1].bid[0].crid); + } else { + expect(response[1].creativeId).to.equal(bidResponses.body.seatbid[1].bid[0].id); + } + expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); + expect(response[1].currency).to.equal('USD'); + expect(response[1].netRevenue).to.equal(false); + expect(response[1].ttl).to.equal(300); + expect(response[1].referrer).to.include(utils.getTopWindowUrl()); + expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); }); it('should check for dealChannel value selection', () => { @@ -220,6 +248,7 @@ describe('PubMatic adapter', () => { let response = spec.interpretResponse(bidResponses, request); expect(response).to.be.an('array').with.length.above(0); expect(response[0].dealChannel).to.equal('PMPG'); + expect(response[1].dealChannel).to.equal('PREF'); }); it('should check for unexpected dealChannel value selection', () => { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 7dcb5e3bbe6..f0ffee12806 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -172,7 +172,7 @@ describe('the rubicon adapter', () => { }, position: 'atf', referrer: 'localhost', - latLong: [40.7608, '111.8910'] + latLong: [40.7607823, '111.8910325'] }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', @@ -256,6 +256,66 @@ describe('the rubicon adapter', () => { }); }); + it('should make a well-formed request object without latLong', () => { + let expectedQuery = { + 'account_id': '14062', + 'site_id': '70608', + 'zone_id': '335918', + 'size_id': '15', + 'alt_size_ids': '43', + 'p_pos': 'atf', + 'rp_floor': '0.01', + 'rp_secure': /[01]/, + 'rand': '0.1', + 'tk_flint': INTEGRATION, + 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'p_screen_res': /\d+x\d+/, + 'tk_user_key': '12346', + 'kw': 'a,b,c', + 'tg_v.ucat': 'new', + 'tg_v.lastsearch': 'iphone', + 'tg_i.rating': '5-star', + 'tg_i.prodtype': 'tech', + 'rf': 'localhost', + 'p_geo.latitude': undefined, + 'p_geo.longitude': undefined + }; + + sandbox.stub(Math, 'random').callsFake(() => 0.1); + + delete bidderRequest.bids[0].params.latLong; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + + expect(request.url).to.equal('//fastlane.rubiconproject.com/a/api/fastlane.json'); + + // test that all values above are both present and correct + Object.keys(expectedQuery).forEach(key => { + let value = expectedQuery[key]; + if (value instanceof RegExp) { + expect(data[key]).to.match(value); + } else { + expect(data[key]).to.equal(value); + } + }); + + bidderRequest.bids[0].params.latLong = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(request.url).to.equal('//fastlane.rubiconproject.com/a/api/fastlane.json'); + + // test that all values above are both present and correct + Object.keys(expectedQuery).forEach(key => { + let value = expectedQuery[key]; + if (value instanceof RegExp) { + expect(data[key]).to.match(value); + } else { + expect(data[key]).to.equal(value); + } + }); + }); + it('page_url should use params.referrer, config.getConfig("pageUrl"), utils.getTopWindowUrl() in that order', () => { sandbox.stub(utils, 'getTopWindowUrl').callsFake(() => 'http://www.prebid.org'); @@ -1206,6 +1266,66 @@ describe('the rubicon adapter', () => { syncs = spec.getUserSyncs(); expect(syncs).to.equal(undefined); }); + + it('should pass gdpr params if consent is true', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo` + }); + }); + + it('should pass gdpr params if consent is false', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=0&gdpr_consent=foo` + }); + }); + + it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: 'foo' + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo` + }); + }); + + it('should pass no params if gdpr consentString is not defined', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {})).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}` + }); + }); + + it('should pass no params if gdpr consentString is a number', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: 0 + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}` + }); + }); + + it('should pass no params if gdpr consentString is null', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: null + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}` + }); + }); + + it('should pass no params if gdpr consentString is a object', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: {} + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}` + }); + }); + + it('should pass no params if gdpr is not defined', () => { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined)).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}` + }); + }); }); }); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 82c2098f234..57c070e9748 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -99,6 +99,51 @@ describe('Smart bid adapter tests', () => { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); + it('Verify build request with GDPR', () => { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + + it('Verify build request with GDPR without gdprApplies', () => { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + it('Verify parse response', () => { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -116,7 +161,11 @@ describe('Smart bid adapter tests', () => { expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); expect(bid.referrer).to.equal(utils.getTopWindowUrl()); - expect(function() { spec.interpretResponse(BID_RESPONSE, {data: 'invalid Json'}) }).to.not.throw(); + expect(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); }); it('Verifies bidder code', () => { @@ -187,15 +236,21 @@ describe('Smart bid adapter tests', () => { }); it('Verifies user sync', () => { - var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE]); + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE]); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.equal('http://awesome.fake.csync.url'); - syncs = spec.getUserSyncs({iframeEnabled: false}, [BID_RESPONSE]); + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE]); expect(syncs).to.have.lengthOf(0); - syncs = spec.getUserSyncs({iframeEnabled: true}, []); + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); expect(syncs).to.have.lengthOf(0); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 9218409c46c..6860605d343 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -789,4 +789,32 @@ describe('Utils', function () { expect(test2).to.equal(var2); }); }); + + describe('getAdUnitSizes', () => { + it('returns an empty response when adUnits is undefined', () => { + let sizes = utils.getAdUnitSizes(); + expect(sizes).to.be.undefined; + }); + + it('returns an empty array when invalid data is present in adUnit object', () => { + let sizes = utils.getAdUnitSizes({ sizes: 300 }); + expect(sizes).to.deep.equal([]); + }); + + it('retuns an array of arrays when reading from adUnit.sizes', () => { + let sizes = utils.getAdUnitSizes({ sizes: [300, 250] }); + expect(sizes).to.deep.equal([[300, 250]]); + + sizes = utils.getAdUnitSizes({ sizes: [[300, 250], [300, 600]] }); + expect(sizes).to.deep.equal([[300, 250], [300, 600]]); + }); + + it('returns an array of arrays when reading from adUnit.mediaTypes.banner.sizes', () => { + let sizes = utils.getAdUnitSizes({ mediaTypes: { banner: { sizes: [300, 250] } } }); + expect(sizes).to.deep.equal([[300, 250]]); + + sizes = utils.getAdUnitSizes({ mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } } }); + expect(sizes).to.deep.equal([[300, 250], [300, 600]]); + }); + }); });