diff --git a/gulpfile.js b/gulpfile.js index ced29b266a7..03a0a4a7559 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -76,7 +76,10 @@ function escapePostbidConfig() { }; escapePostbidConfig.displayName = 'escape-postbid-config'; -function lint() { +function lint(done) { + if (argv.nolint) { + return done(); + } return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js']) .pipe(eslint()) .pipe(eslint.format('stylish')) diff --git a/integrationExamples/gpt/hello_world_adikteev.html b/integrationExamples/gpt/hello_world_adikteev.html new file mode 100644 index 00000000000..7372ff12d7f --- /dev/null +++ b/integrationExamples/gpt/hello_world_adikteev.html @@ -0,0 +1,93 @@ + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 88d4839d984..6852b9f680a 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -312,8 +312,15 @@ width: '300', height: '250', } - } - + }, + { + bidder: 'adikteev', + params: { + placementId: 12345, + currency: 'EUR', + bidFloorPrice: 0.1, + } + }, ] }, { code: 'div-gpt-ad-12345678-1', diff --git a/modules/adikteevBidAdapter.js b/modules/adikteevBidAdapter.js new file mode 100644 index 00000000000..12d502de94a --- /dev/null +++ b/modules/adikteevBidAdapter.js @@ -0,0 +1,94 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {config} from 'src/config'; + +export const BIDDER_CODE = 'adikteev'; +export const ENDPOINT_URL = 'https://serve-adserver.adikteev.com/api/prebid/bid'; +export const ENDPOINT_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/bid'; +export const USER_SYNC_IFRAME_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IFRAME_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IMAGE_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-image'; +export const USER_SYNC_IMAGE_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-image'; + +export let stagingEnvironmentSwitch = false; // Don't use it. Allow us to make tests on staging + +export function setstagingEnvironmentSwitch(value) { + stagingEnvironmentSwitch = value; +} + +function validateSizes(sizes) { + if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { + return false; + } + return sizes.every(size => utils.isArray(size) && size.length === 2); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + setstagingEnvironmentSwitch(stagingEnvironmentSwitch || !!bid.params.stagingEnvironment); + return !!( + bid && + bid.params && + bid.params.bidFloorPrice && + bid.params.placementId && + bid.bidder === BIDDER_CODE && + validateSizes(bid.mediaTypes.banner.sizes) + ); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const payload = { + validBidRequests, + bidderRequest, + refererInfo: bidderRequest.refererInfo, + currency: config.getConfig('currency'), + userAgent: navigator.userAgent, + screen: { + width: window.screen.width, + height: window.screen.height + }, + language: navigator.language, + cookies: document.cookie.split(';'), + prebidUpdateVersion: '1.29.0', + }; + return { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (serverResponse, bidRequests) => { + const returnedBids = []; + const validBidRequests = JSON.parse(bidRequests.data).validBidRequests; + serverResponse.body.forEach((value, index) => { + const overrides = { + requestId: validBidRequests[index].bidId, + }; + returnedBids.push(Object.assign({}, value, overrides)); + }); + return returnedBids; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: stagingEnvironmentSwitch ? USER_SYNC_IFRAME_URL_STAGING : USER_SYNC_IFRAME_URL, + }); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'image', + url: stagingEnvironmentSwitch ? USER_SYNC_IMAGE_URL_STAGING : USER_SYNC_IMAGE_URL, + }); + } + return syncs; + }, +}; +registerBidder(spec); diff --git a/modules/adikteevBidAdapter.md b/modules/adikteevBidAdapter.md new file mode 100644 index 00000000000..d5008f61b03 --- /dev/null +++ b/modules/adikteevBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Adikteev Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adnetwork@adikteev.com +``` + +# Description + +Module that connects to Adikteev's demand sources + +# Test Parameters + +``` javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[750, 200]], // a display size + } + }, + bids: [ + { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + } + } + ] + } + ]; +``` diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index aaec207dc1e..19aa5e7cf73 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -233,7 +233,9 @@ function newBid(serverBid, rtbBid, bidderRequest) { netRevenue: true, ttl: 300, appnexus: { - buyerMemberId: rtbBid.buyer_member_id + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code } }; diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js new file mode 100644 index 00000000000..042251ea035 --- /dev/null +++ b/modules/emx_digitalBidAdapter.js @@ -0,0 +1,124 @@ +import * as utils from 'src/utils'; +import { + registerBidder +} from 'src/adapters/bidderFactory'; +import { + BANNER +} from 'src/mediaTypes'; +import { + config +} from 'src/config'; + +const BIDDER_CODE = 'emx_digital'; +const ENDPOINT = 'hb.emxdgt.com'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.params.tagid); + }, + buildRequests: function (validBidRequests, bidRequests) { + const {host, href, protocol} = utils.getTopWindowLocation(); + let emxData = {}; + let emxImps = []; + const auctionId = bidRequests.auctionId; + const timeout = config.getConfig('bidderTimeout'); + const timestamp = Date.now(); + const url = location.protocol + '//' + ENDPOINT + ('?t=' + timeout + '&ts=' + timestamp); + + utils._each(validBidRequests, function (bid) { + let tagId = String(utils.getBidIdParameter('tagid', bid.params)); + let bidFloor = utils.getBidIdParameter('bidfloor', bid.params) || 0; + let emxBid = { + id: bid.bidId, + tid: bid.transactionId, + tagid: tagId, + secure: protocol === 'https:' ? 1 : 0, + banner: { + format: bid.sizes.map(function (size) { + return { + w: size[0], + h: size[1] + }; + }), + w: bid.sizes[0][0], + h: bid.sizes[0][1] + } + } + if (bidFloor > 0) { + emxBid.bidfloor = bidFloor + } + emxImps.push(emxBid); + }); + emxData = { + id: auctionId, + imp: emxImps, + site: { + domain: host, + page: href + } + }; + if (bidRequests.gdprConsent) { + emxData.regs = { + ext: { + gdpr: bidRequests.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (bidRequests.gdprConsent && bidRequests.gdprConsent.gdprApplies) { + emxData.user = { + ext: { + consent: bidRequests.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: url, + data: JSON.stringify(emxData), + options: { + withCredentials: true + } + }; + }, + interpretResponse: function (serverResponse) { + let emxBidResponses = []; + let response = serverResponse.body || {}; + if (response.seatbid && response.seatbid.length > 0 && response.seatbid[0].bid) { + response.seatbid.forEach(function (emxBid) { + emxBid = emxBid.bid[0]; + emxBidResponses.push({ + requestId: emxBid.id, + cpm: emxBid.price, + width: emxBid.w, + height: emxBid.h, + creativeId: emxBid.crid || emxBid.id, + dealId: emxBid.dealid || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(emxBid.adm), + ttl: emxBid.ttl + }); + }); + } + return emxBidResponses; + }, + getUserSyncs: function (syncOptions) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//biddr.brealtime.com/check.html' + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: '//edba.brealtime.com/' + }); + } + return syncs; + } +}; +registerBidder(spec); diff --git a/modules/emx_digitalBidAdapter.md b/modules/emx_digitalBidAdapter.md new file mode 100644 index 00000000000..c9435e2f1d1 --- /dev/null +++ b/modules/emx_digitalBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: EMX Digital Adapter +Module Type: Bidder Adapter +Maintainer: git@emxdigital.com +``` + +# Description + +The EMX Digital adapter provides publishers with access to the EMX Marketplace. The adapter is GDPR compliant. Please note that the adapter supports Banner media type only. + +Note: The EMX Digital adapter requires approval and implementation guidelines from the EMX team, including existing publishers that work with EMX Digital. Please reach out to your account manager or prebid@emxdigital.com for more information. + +The bidder code should be ```emx_digital``` +The params used by the bidder are : +```tagid``` - string (mandatory) +```bidfloor``` - string (optional) + +# Test Parameters +``` +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600] + } + }, + bids: [ + { + bidder: 'emx_digital', + params: { + tagid: '25251', + } + }] +}]; +``` diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js new file mode 100644 index 00000000000..660b5c66a78 --- /dev/null +++ b/modules/gridBidAdapter.js @@ -0,0 +1,151 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'grid'; +const ENDPOINT_URL = '//grid.bidswitch.net/hb'; +const TIME_TO_LIVE = 360; +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 - ' +}; +export const spec = { + code: BIDDER_CODE, + /** + * 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: function(bid) { + return !!bid.params.uid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let reqId; + + bids.forEach(bid => { + reqId = bid.bidderRequestId; + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + }); + + const payload = { + u: utils.getTopWindowUrl(), + auids: auids.join(','), + r: reqId + }; + + if (bidderRequest) { + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + + 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, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + } +} + +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, 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, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: false, + 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/gridBidAdapter.md b/modules/gridBidAdapter.md new file mode 100755 index 00000000000..9b7b0e0515e --- /dev/null +++ b/modules/gridBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: The Grid Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "grid", + params: { + uid: '1', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "grid", + params: { + uid: 2, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 0e7e28b9b6b..27d01e677ef 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -59,6 +59,7 @@ export const spec = { if (bidderRequest && bidderRequest.gdprConsent) { data.consent = { + gdprVendorId: 150, gdprConsentString: bidderRequest.gdprConsent.consentString, // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true gdprConsentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 48b6805c0e1..c31f485020e 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -122,6 +122,16 @@ export const spec = { } +export let pixel = { + fire(url) { + let img = document.createElement('img') + img.src = url + img.id = 'jp-pixel-track' + img.style.cssText = 'display:none !important;' + document.body.appendChild(img) + } +}; + function track (data, payload, type) { let pubUrl = '' @@ -147,11 +157,7 @@ ru=${encodeURIComponent(pubUrl)}&tt=&siw=&sh=${payload.sh}&sw=${payload.sw}&wh=$ sd=&_c=&et=&aid=&said=&ei=&fc=&sp=&at=bidder&cid=&ist=&mg=&dl=&dlt=&ev=&vt=&zid=${payload.id}&dr=${duration}&di=&pr=& cw=&ch=&nt=&st=&jp=${encodeURIComponent(JSON.stringify(jp))}&ty=${type}` - let img = document.createElement('img') - img.src = pixelUrl - img.id = 'jp-pixel-track' - img.style.cssText = 'display:none !important;' - document.body.appendChild(img) + pixel.fire(pixelUrl); } function findBid (params, bids) { diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index 5b1fd999ee6..bd341dfd79f 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -136,7 +136,6 @@ function isConsentRequired(consentData) { function getRequestData(bid, consentData) { let loc = utils.getTopWindowLocation(); - let global = (window.top) ? window.top : window; let page = (bid.params.site && bid.params.site.page) ? (bid.params.site.page) : (loc.href); let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : utils.getTopWindowReferrer(); let bidData = { @@ -160,7 +159,7 @@ function getRequestData(bid, consentData) { ref: ref }, device: { - ua: global.navigator.userAgent + ua: navigator.userAgent }, tmax: 200 }; diff --git a/modules/openxoutstreamBidAdapter.js b/modules/openxoutstreamBidAdapter.js index dde101f25d5..aee260e0a11 100644 --- a/modules/openxoutstreamBidAdapter.js +++ b/modules/openxoutstreamBidAdapter.js @@ -20,28 +20,21 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function(bidRequest) { - if (bidRequest.params.delDomain) { - return !!bidRequest.params.unit || utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } - return false; + return !!(bidRequest.params.delDomain && bidRequest.params.unit) }, buildRequests: function(bidRequests, bidderRequest) { if (bidRequests.length === 0) { return []; } let requests = []; - requests.push(buildOXBannerRequest(bidRequests, bidderRequest)); + bidRequests.forEach(bid => { + requests.push(buildOXBannerRequest(bid, bidderRequest)); + }) return requests; }, - interpretResponse: function(serverResponse, serverRequest) { - return handleVastResponse(serverResponse, serverRequest.payload) + interpretResponse: function(bid, serverResponse) { + return handleVastResponse(bid, serverResponse.payload) }, - - transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ - 'unit': 'string', - }, params); - } }; function getViewportDimensions(isIfr) { @@ -70,7 +63,7 @@ function getViewportDimensions(isIfr) { return `${width}x${height}`; } -function buildCommonQueryParamsFromBids(bids, bidderRequest) { +function buildCommonQueryParamsFromBids(bid, bidderRequest) { const isInIframe = utils.inIframe(); let defaultParams; defaultParams = { @@ -82,13 +75,13 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { tz: new Date().getTimezoneOffset(), tws: getViewportDimensions(isInIframe), be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - auid: '540141567', - dddid: utils._map(bids, bid => bid.transactionId).join(','), + bc: bid.params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, + auid: bid.params.unit, + dddid: bid.transactionId, openrtb: '%7B%22mimes%22%3A%5B%22video%2Fmp4%22%5D%7D', nocache: new Date().getTime(), - vht: bids[0].params.height || bids[0].sizes[0][1], - vwd: bids[0].params.width || bids[0].sizes[0][0] + vht: bid.params.height || bid.sizes[0][1], + vwd: bid.params.width || bid.sizes[0][0] }; if (utils.deepAccess(bidderRequest, 'gdprConsent')) { @@ -110,24 +103,24 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { return defaultParams; } -function buildOXBannerRequest(bids, bidderRequest) { - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.sizes).join(',')).join('|'); +function buildOXBannerRequest(bid, bidderRequest) { + let queryParams = buildCommonQueryParamsFromBids(bid, bidderRequest); + queryParams.aus = utils.parseSizesInput(bid.sizes).join(','); - if (bids.some(bid => bid.params.doNotTrack)) { + if (bid.params.doNotTrack) { queryParams.ns = 1; } - if (bids.some(bid => bid.params.coppa)) { + if (bid.params.coppa) { queryParams.tfcd = 1; } - let url = `https://${bids[0].params.delDomain}/v/1.0/avjp` + let url = `https://${bid.params.delDomain}/v/1.0/avjp` return { method: 'GET', url: url, data: queryParams, - payload: {'bids': bids} + payload: {'bid': bid} }; } @@ -146,7 +139,7 @@ function handleVastResponse(response, serverResponse) { const ymAdsScript = ''; let bidResponse = {}; - bidResponse.requestId = serverResponse.bids[0].bidId; + bidResponse.requestId = serverResponse.bid.bidId; bidResponse.bidderCode = BIDDER_CODE; bidResponse.netRevenue = NET_REVENUE; bidResponse.currency = CURRENCY; diff --git a/modules/openxoutstreamBidAdapter.md b/modules/openxoutstreamBidAdapter.md index a77b4560f97..16d66b92409 100644 --- a/modules/openxoutstreamBidAdapter.md +++ b/modules/openxoutstreamBidAdapter.md @@ -24,9 +24,8 @@ var adUnits = [ { bidder: 'openxoutstream', params: { - unit: '53943996499', + unit: '540141567', delDomain: 'se-demo-d.openx.net', - publisher_page_url: 'yieldmo.com', width: '300', height: '250', } diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index e6f4d27bdbb..79128a834a4 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from 'src/utils'; +import { ajax } from 'src/ajax'; import { registerBidder } from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'quantcast'; @@ -69,7 +70,7 @@ export const spec = { }); }); - const gdprConsent = bidderRequest ? bidderRequest.gdprConsent : {}; + const gdprConsent = (bidderRequest && bidderRequest.gdprConsent) ? bidderRequest.gdprConsent : {}; // Request Data Format can be found at https://wiki.corp.qc/display/adinf/QCX const requestData = { @@ -157,6 +158,10 @@ export const spec = { }); return bidResponsesList; + }, + onTimeout(timeoutData) { + const url = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; + ajax(url, null, null); } }; diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c8616894826..14143f5f21d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,11 +1,12 @@ import {ajax} from 'src/ajax'; -import includes from 'core-js/library/fn/array/includes'; import adapter from 'src/AnalyticsAdapter'; +import find from 'core-js/library/fn/array/find'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; -import { logInfo, generateUUID } from 'src/utils'; +import { logInfo, generateUUID, timestamp } from 'src/utils'; const analyticsType = 'endpoint'; +const rivrUsrIdCookieKey = 'rvr_usr_id'; const DEFAULT_HOST = 'tracker.rivr.simplaex.com'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -23,18 +24,12 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { rivrAnalytics.context.queue.init(); } if (rivrAnalytics.context.auctionObject) { - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); - saveUnoptimisedParams(); + rivrAnalytics.context.auctionObject = createNewAuctionObject(); + saveUnoptimisedAdUnits(); fetchLocalization(); } handler = trackAuctionInit; break; - case CONSTANTS.EVENTS.BID_REQUESTED: - handler = trackBidRequest; - break; - case CONSTANTS.EVENTS.BID_RESPONSE: - handler = trackBidResponse; - break; case CONSTANTS.EVENTS.BID_WON: handler = trackBidWon; break; @@ -56,7 +51,7 @@ export function sendAuction() { removeEmptyProperties(rivrAnalytics.context.auctionObject); let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + rivrAnalytics.context.auctionObject = createNewAuctionObject(); logInfo('sending request to analytics => ', req); ajax( `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, @@ -98,46 +93,91 @@ function trackAuctionInit(args) { rivrAnalytics.context.auctionObject.id = args.auctionId; }; -function trackBidRequest(args) { - setCurrentPublisherId(args); - let bidRequest = args; - rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); -}; - -function trackBidResponse(args) { - let bidResponse = createBidResponse(args); - rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); +function trackBidWon(args) { + setWinningBidStatus(args); }; -function trackBidWon(args) { +function setWinningBidStatus(args) { let auctionObject = rivrAnalytics.context.auctionObject; - let auctionImpression = createAuctionImpression(args); - auctionObject.imp.push(auctionImpression); - assignBidWonStatusToResponse(args); + const bidderObjectForThisWonBid = find(auctionObject.bidders, (bidder) => { + return bidder.id === args.bidderCode; + }); + if (bidderObjectForThisWonBid) { + const bidObjectForThisWonBid = find(bidderObjectForThisWonBid.bids, (bid) => { + return bid.impId === args.adUnitCode; + }); + if (bidObjectForThisWonBid) { + bidObjectForThisWonBid.status = 1; + } + } }; -function trackAuctionEnd(args) { +export function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); - fillBidResponsesOfUnrespondedBidRequests(); -}; - -function trackBidTimeout(args) { - return [args]; + rivrAnalytics.context.auctionObject.bidders = buildBiddersArrayFromAuctionEnd(args); + rivrAnalytics.context.auctionObject.impressions = buildImpressionsArrayFromAuctionEnd(args); }; -function setCurrentPublisherId(bidRequested) { - let site = rivrAnalytics.context.auctionObject.site; - let app = rivrAnalytics.context.auctionObject.app; - let pubId = rivrAnalytics.context.pubId; - if (!site.publisher.id || app.publisher.id) { - if (pubId) { - site.publisher.id = pubId; - app.publisher.id = pubId; - } else { - site.publisher.id = bidRequested.bids[0].crumbs.pubcid; - app.publisher.id = bidRequested.bids[0].crumbs.pubcid; +function buildImpressionsArrayFromAuctionEnd(auctionEndEvent) { + return auctionEndEvent.adUnits.map((adUnit) => { + const impression = {}; + impression.id = adUnit.code; + impression.adType = 'unknown'; + impression.acceptedSizes = []; + const bidReceivedForThisAdUnit = find(auctionEndEvent.bidsReceived, (bidReceived) => { + return adUnit.code === bidReceived.adUnitCode; + }); + if (adUnit.mediaTypes) { + if (adUnit.mediaTypes.banner) { + buildAdTypeDependentFieldsForImpression(impression, 'banner', adUnit, bidReceivedForThisAdUnit); + } else if (adUnit.mediaTypes.video) { + buildAdTypeDependentFieldsForImpression(impression, 'video', adUnit, bidReceivedForThisAdUnit); + } } + return impression; + }); +} + +function buildAdTypeDependentFieldsForImpression(impression, adType, adUnit, bidReceivedForThisAdUnit) { + impression.adType = adType; + impression.acceptedSizes = adUnit.mediaTypes[adType].sizes.map((acceptedSize) => { + return { + w: acceptedSize[0], + h: acceptedSize[1] + }; + }); + if (bidReceivedForThisAdUnit) { + impression[adType] = { + w: bidReceivedForThisAdUnit.width, + h: bidReceivedForThisAdUnit.height + }; } +} + +function buildBiddersArrayFromAuctionEnd(auctionEndEvent) { + return auctionEndEvent.bidderRequests.map((bidderRequest) => { + const bidder = {}; + bidder.id = bidderRequest.bidderCode; + bidder.bids = bidderRequest.bids.map((bid) => { + const bidReceivedForThisRequest = find(auctionEndEvent.bidsReceived, (bidReceived) => { + return bidderRequest.bidderCode === bidReceived.bidderCode && + bid.bidId === bidReceived.adId && + bid.adUnitCode === bidReceived.adUnitCode; + }); + return { + adomain: [''], + clearPrice: 0.0, + impId: bid.adUnitCode, + price: bidReceivedForThisRequest ? bidReceivedForThisRequest.cpm : 0.0, + status: 0 + }; + }); + return bidder; + }); +} + +function trackBidTimeout(args) { + return [args]; }; export function fetchLocalization() { @@ -158,70 +198,10 @@ export function setAuctionAbjectPosition(position) { } function getPlatformType() { - if (navigator.userAgent.match(/mobile/i)) { - return 'Mobile'; - } else if (navigator.userAgent.match(/iPad|Android|Touch/i)) { - return 'Tablet'; + if (navigator.userAgent.match(/mobile/i) || navigator.userAgent.match(/iPad|Android|Touch/i)) { + return 1; } else { - return 'Desktop'; - } -}; - -function createBidResponse(bidResponseEvent) { - return { - timestamp: bidResponseEvent.responseTimestamp, - status: bidResponseEvent.getStatusCode(), - total_duration: bidResponseEvent.timeToRespond, - bidderId: null, - bidder_name: bidResponseEvent.bidder, - cur: bidResponseEvent.currency, - seatbid: [ - { - seat: null, - bid: [ - { - status: 2, - clear_price: bidResponseEvent.cpm, - attr: [], - crid: bidResponseEvent.creativeId, - cid: null, - id: null, - adid: bidResponseEvent.adId, - adomain: [], - iurl: null - } - ] - } - ] - } -}; - -function createSingleEmptyBidResponse(bidResponse) { - return { - timestamp: bidResponse.start, - total_duration: null, - bidderId: null, - bidder_name: bidResponse.bidder, - cur: null, - response: 'noBid', - seatbid: [] - } -}; - -function createAuctionImpression(bidWonEvent) { - return { - tagid: bidWonEvent.adUnitCode, - displaymanager: null, - displaymanagerver: null, - secure: null, - bidfloor: null, - banner: { - w: bidWonEvent.width, - h: bidWonEvent.height, - pos: null, - expandable: [], - api: [] - } + return 2; } }; @@ -314,75 +294,40 @@ function addHandlers(bannersIds) { }) }; -function fulfillAuctionObject() { - let newAuction = { - id: null, - timestamp: null, - at: null, - bcat: [], - imp: [], - app: { - id: null, - name: null, - domain: window.location.href, - bundle: null, - cat: [], - publisher: { - id: null, - name: null - } +export function createNewAuctionObject() { + const auction = { + id: '', + publisher: rivrAnalytics.context.clientID, + blockedCategories: [''], + timestamp: timestamp(), + user: { + id: rivrAnalytics.context.userId }, site: { - id: null, - name: null, - domain: window.location.href, - cat: [], - publisher: { - id: null, - name: null - } + domain: window.location.host, + page: window.location.pathname, + categories: rivrAnalytics.context.siteCategories }, + impressions: [], + bidders: [], device: { - geo: { - city: null, - country: null, - region: null, - zip: null, - type: null, - metro: null - }, - devicetype: getPlatformType(), - osv: null, - os: null, - model: null, - make: null, - carrier: null, - ip: null, - didsha1: null, - dpidmd5: null, - ext: { - uid: null - } - }, - user: { - id: null, - yob: null, - gender: null, + userAgent: navigator.userAgent, + browser: '', + deviceType: getPlatformType() }, - bidResponses: [], - bidRequests: [], + 'ext.rivr.originalvalues': [], 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', modelVersion: localStorage.getItem('rivr_model_version') || null, - 'ext.rivr.originalvalues': [] - }; - return newAuction; + } + + return auction; }; -function saveUnoptimisedParams() { +export function saveUnoptimisedAdUnits() { let units = rivrAnalytics.context.adUnits; if (units) { if (units.length > 0) { - let allUnits = connectAllUnits(units); + let allUnits = concatAllUnits(units); allUnits.forEach((adUnit) => { adUnit.bids.forEach((bid) => { let configForBidder = fetchConfigForBidder(bid.bidder); @@ -396,11 +341,8 @@ function saveUnoptimisedParams() { } }; -function connectAllUnits(units) { - return units.reduce((acc, units) => { - units.forEach((unit) => acc.push(unit)) - return acc - }, []); +export function concatAllUnits(units) { + return Array.prototype.concat.apply([], units); } export function createUnOptimisedParamsField(bid, config) { @@ -474,41 +416,6 @@ export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { } }; -function assignBidWonStatusToResponse(wonBid) { - let wonBidId = wonBid.adId; - rivrAnalytics.context.auctionObject.bidResponses.forEach((response) => { - if (response.seatbid.length > 0) { - let bidObjectResponse = response.seatbid[0].bid[0]; - if (wonBidId === bidObjectResponse.adid) { - bidObjectResponse.status = 1 - } - } - }); -}; - -function fillBidResponsesOfUnrespondedBidRequests() { - let unRespondedBidRequests = getAllUnrespondedBidRequests(); - unRespondedBidRequests.forEach((bid) => { - let emptyBidResponse = createSingleEmptyBidResponse(bid); - rivrAnalytics.context.auctionObject.bidResponses.push(emptyBidResponse); - }); -}; - -function getAllUnrespondedBidRequests() { - let respondedBidIds = getAllRespondedBidIds(); - let bidRequests = rivrAnalytics.context.auctionObject.bidRequests; - let allNotRespondedBidRequests = bidRequests.reduce((cache, requestBidder) => { - let notRespondedBids = requestBidder.bids.filter((bid) => !includes(respondedBidIds, bid.bidId)); - notRespondedBids.forEach((bid) => bid.start = requestBidder.start); - return cache.concat(notRespondedBids); - }, []); - return allNotRespondedBidRequests; -}; - -function getAllRespondedBidIds() { - return rivrAnalytics.context.auctionObject.bidResponses.map((response) => response.seatbid[0].bid[0].adid); -}; - function removeEmptyProperties(obj) { Object.keys(obj).forEach(function(key) { if (obj[key] && typeof obj[key] === 'object') removeEmptyProperties(obj[key]) @@ -516,6 +423,16 @@ function removeEmptyProperties(obj) { }); }; +function getCookie(name) { + var value = '; ' + document.cookie; + var parts = value.split('; ' + name + '='); + if (parts.length == 2) return parts.pop().split(';').shift(); +} + +function storeAndReturnRivrUsrIdCookie() { + return document.cookie = 'rvr_usr_id=' + generateUUID(); +} + // save the base class function rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; @@ -527,10 +444,11 @@ rivrAnalytics.enableAnalytics = (config) => { copiedUnits = JSON.parse(stringifiedAdUnits); } rivrAnalytics.context = { + userId: getCookie(rivrUsrIdCookieKey) || storeAndReturnRivrUsrIdCookie(), host: config.options.host || DEFAULT_HOST, - pubId: config.options.pubId, auctionObject: {}, adUnits: copiedUnits, + siteCategories: config.options.siteCategories || [], clientID: config.options.clientID, authToken: config.options.authToken, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) diff --git a/modules/rivrAnalyticsAdapter.md b/modules/rivrAnalyticsAdapter.md new file mode 100644 index 00000000000..787034e362e --- /dev/null +++ b/modules/rivrAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Rivr Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: rnd@simplaex.com + +# Description + +Analytics adapter for www.rivr.ai. + +Contact support@simplaex.com for information and support. diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index e4a30782dbe..0be3887637d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -43,9 +43,12 @@ export const spec = { const request = { id: validBidRequests[0].auctionId, imp: validBidRequests.map(slot => mapImpression(slot)), - site: mapSite(validBidRequests), + site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, - test: validBidRequests[0].params.test || 0 + test: validBidRequests[0].params.test || 0, + source: { + tid: validBidRequests[0].transactionId + } }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) @@ -89,12 +92,19 @@ registerBidder(spec); * @returns {object} Imp by OpenRTB 2.5 §3.2.4 */ function mapImpression(slot) { - return { + const imp = { id: slot.bidId, banner: mapBanner(slot), native: mapNative(slot), tagid: slot.adUnitCode.toString() }; + + const bidfloor = parseFloat(slot.params.bidfloor); + if (bidfloor) { + imp.bidfloor = bidfloor + } + + return imp; } /** @@ -118,9 +128,10 @@ function mapBanner(slot) { /** * @param {object} slot Ad Unit Params by Prebid + * @param {object} bidderRequest by Prebid * @returns {object} Site by OpenRTB 2.5 §3.2.13 */ -function mapSite(slot) { +function mapSite(slot, bidderRequest) { const pubId = slot && slot.length > 0 ? slot[0].params.publisherId : 'unknown'; @@ -128,7 +139,7 @@ function mapSite(slot) { publisher: { id: pubId.toString(), }, - page: utils.getTopWindowUrl(), + page: bidderRequest.refererInfo.referer, name: utils.getOrigin() } } diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index a2d7e2aedda..c847e688941 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -7,7 +7,7 @@ Maintainer: prebid@rtbhouse.com # Description Connects to RTB House unique demand. -Banner formats are supported. +Banner and native formats are supported. Unique publisherId is required. Please reach out to pmp@rtbhouse.com to receive your own @@ -23,7 +23,8 @@ Please reach out to pmp@rtbhouse.com to receive your own bidder: "rtbhouse", params: { region: 'prebid-eu', - publisherId: 'PREBID_TEST_ID' + publisherId: 'PREBID_TEST_ID', + bidfloor: 0.01 // optional } } ] @@ -50,6 +51,7 @@ Please reach out to pmp@rtbhouse.com to receive your own params: { region: 'prebid-eu', publisherId: 'PREBID_TEST_ID' + bidfloor: 0.01 // optional } } ] diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index dd99df1fc7e..6d4ced88ba1 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,11 +2,44 @@ * Adapter to send bids to Undertone */ -import * as utils from 'src/utils'; +import * as urlUtils from 'src/url'; import { registerBidder } from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'undertone'; const URL = '//hb.undertone.com/hb'; +const FRAME_USER_SYNC = '//cdn.undertone.com/js/usersync.html'; +const PIXEL_USER_SYNC_1 = '//usr.undertone.com/userPixel/syncOne?id=1&of=2'; +const PIXEL_USER_SYNC_2 = '//usr.undertone.com/userPixel/syncOne?id=2&of=2'; + +function getCanonicalUrl() { + try { + let doc = window.top.document; + let element = doc.querySelector("link[rel='canonical']"); + if (element !== null) { + return element.href; + } + } catch (e) { + } + return null; +} + +function extractDomainFromHost(pageHost) { + let domain = null; + try { + let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); + if (domains != null && domains.length > 0) { + domain = domains[0]; + for (let i = 1; i < domains.length; i++) { + if (domains[i].length > domain.length) { + domain = domains[i]; + } + } + } + } catch (e) { + domain = null; + } + return domain; +} export const spec = { code: BIDDER_CODE, @@ -16,21 +49,14 @@ export const spec = { return true; } }, - buildRequests: function(validBidRequests) { + buildRequests: function(validBidRequests, bidderRequest) { const payload = { 'x-ut-hb-params': [] }; - const location = utils.getTopWindowLocation(); - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(location.host); - let domain = null; - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } + const referer = bidderRequest.refererInfo.referer; + const hostname = urlUtils.parse(referer).hostname; + let domain = extractDomainFromHost(hostname); + const pageUrl = getCanonicalUrl() || referer; const pubid = validBidRequests[0].params.publisherId; const REQ_URL = `${URL}?pid=${pubid}&domain=${domain}`; @@ -39,7 +65,7 @@ export const spec = { const bid = { bidRequestId: bidReq.bidId, hbadaptor: 'prebid', - url: location.href, + url: pageUrl, domain: domain, placementId: bidReq.params.placementId != undefined ? bidReq.params.placementId : null, publisherId: bidReq.params.publisherId, @@ -78,6 +104,29 @@ export const spec = { }); } return bids; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const syncs = []; + if (gdprConsent && gdprConsent.gdprApplies === true) { + return syncs; + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: FRAME_USER_SYNC + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: PIXEL_USER_SYNC_1 + }, + { + type: 'image', + url: PIXEL_USER_SYNC_2 + }); + } + return syncs; } }; registerBidder(spec); diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index cdcab0c705a..5843ce9d339 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from 'src/utils'; import {config} from 'src/config'; import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = '//y.one.impact-ad.jp/h_bid'; @@ -9,15 +10,13 @@ const USER_SYNC_URL = '//y.one.impact-ad.jp/push_sync'; export const spec = { code: BIDDER_CODE, aliases: ['y1'], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { return !!(bid.params.placementId); }, buildRequests: function(validBidRequests) { return validBidRequests.map(bidRequest => { const params = bidRequest.params; - const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; - const width = sizes.split('x')[0]; - const height = sizes.split('x')[1]; const placementId = params.placementId; const cb = Math.floor(Math.random() * 99999999999); const referrer = encodeURIComponent(utils.getTopWindowUrl()); @@ -25,13 +24,24 @@ export const spec = { const payload = { v: 'hb1', p: placementId, - w: width, - h: height, cb: cb, r: referrer, uid: bidId, t: 'i' }; + + const videoMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video'); + if (bidRequest.mediaType === VIDEO || videoMediaType) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; + const size = utils.parseSizesInput(sizes)[0]; + payload.w = size.split('x')[0]; + payload.h = size.split('x')[1]; + } else if ((utils.isEmpty(bidRequest.mediaType) && utils.isEmpty(bidRequest.mediaTypes)) || + (bidRequest.mediaType === BANNER || (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER]))) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; + payload.sz = utils.parseSizesInput(sizes).join(','); + } + return { method: 'GET', url: ENDPOINT_URL, @@ -47,12 +57,12 @@ export const spec = { const height = response.height || 0; const cpm = response.cpm * 1000 || 0; if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; + const dealId = response.dealId || ''; const currency = response.currency || 'JPY'; const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; const referrer = utils.getTopWindowUrl(); const bidResponse = { - requestId: bidRequest.data.uid, + requestId: response.uid, cpm: cpm, width: response.width, height: response.height, @@ -61,9 +71,17 @@ export const spec = { currency: currency, netRevenue: netRevenue, ttl: config.getConfig('_bidderTimeout'), - referrer: referrer, - ad: response.adTag + referrer: referrer }; + + if (response.adTag) { + bidResponse.mediaType = BANNER; + bidResponse.ad = response.adTag; + } else if (response.adm) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = response.adm; + } + bidResponses.push(bidResponse); } return bidResponses; diff --git a/modules/yieldoneBidAdapter.md b/modules/yieldoneBidAdapter.md index b154a2ee781..1414d4e464f 100644 --- a/modules/yieldoneBidAdapter.md +++ b/modules/yieldoneBidAdapter.md @@ -10,20 +10,46 @@ Maintainer: y1dev@platform-one.co.jp Connect to YIELDONE for bids. -THE YIELDONE adapter requires setup and approval from the YIELDONE team. Please reach out to your account team or y1s@platform-one.co.jp for more information. +THE YIELDONE adapter requires setup and approval from the YIELDONE team. +Please reach out to your account team or y1s@platform-one.co.jp for more information. + +Note: THE YIELDONE adapter do not support "multi-format" scenario... if both +banner and video are specified as mediatypes, YIELDONE will treat it as a video unit. # Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'yieldone', - params: { - placementId: '44082' - } - }] - }]; +```javascript +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]] + } + }, + bids: [{ + bidder: 'yieldone', + params: { + placementId: '36891' + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'yieldone', + params: { + placementId: '41993' + } + }] + } ``` ### Configuration diff --git a/package.json b/package.json index a53085fa062..288d970f842 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.30.0-pre", + "version": "1.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/auction.js b/src/auction.js index 61c403e7ff5..221cd519815 100644 --- a/src/auction.js +++ b/src/auction.js @@ -110,7 +110,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) function getProperties() { return { auctionId: _auctionId, - auctionStart: _auctionStart, + timestamp: _auctionStart, auctionEnd: _auctionEnd, auctionStatus: _auctionStatus, adUnits: _adUnits, diff --git a/src/native.js b/src/native.js index b4d2d959b70..c9d274ddccd 100644 --- a/src/native.js +++ b/src/native.js @@ -147,7 +147,7 @@ export function fireNativeTrackers(message, adObject) { } /** - * Gets native targeting key-value paris + * Gets native targeting key-value pairs * @param {Object} bid * @return {Object} targeting */ @@ -163,7 +163,7 @@ export function getNativeTargeting(bid) { value = value.url; } - if (key) { + if (key && value) { keyValues[key] = value; } }); diff --git a/src/targeting.js b/src/targeting.js index dcd59f362c6..30407994b8e 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -35,20 +35,11 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback) { // filter top bid for each bucket by bidder Object.keys(buckets).forEach(bucketKey => { let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(highestCpmCallback, getEmptyBid()))); + Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(highestCpmCallback))); }); return bids; } -function getEmptyBid(adUnitCode) { - return { - adUnitCode: adUnitCode, - cpm: 0, - adserverTargeting: {}, - timeToRespond: 0 - }; -} - /** * @typedef {Object.} targeting * @property {string} targeting_key @@ -222,7 +213,7 @@ export function newTargeting(auctionManager) { .filter(uniques) .map(adUnitCode => bidsReceived .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm, getEmptyBid(adUnitCode))); + .reduce(getHighestCpm)); }; /** diff --git a/src/utils.js b/src/utils.js index 77dfe10c918..93b19485dfe 100644 --- a/src/utils.js +++ b/src/utils.js @@ -66,10 +66,22 @@ exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; */ exports.generateUUID = function generateUUID(placeholder) { return placeholder - ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + ? (placeholder ^ _getRandomData() >> placeholder / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, generateUUID); }; +/** + * Returns random data using the Crypto API if available and Math.random if not + * Method is from https://gist.github.com/jed/982883 like generateUUID, direct link https://gist.github.com/jed/982883#gistcomment-45104 + */ +function _getRandomData() { + if (window && window.crypto && window.crypto.getRandomValues) { + return crypto.getRandomValues(new Uint8Array(1))[0] % 16; + } else { + return Math.random() * 16; + } +} + exports.getBidIdParameter = function (key, paramsObj) { if (paramsObj && paramsObj[key]) { return paramsObj[key]; diff --git a/test/spec/modules/adikteevBidAdapter_spec.js b/test/spec/modules/adikteevBidAdapter_spec.js new file mode 100644 index 00000000000..243cbe2a9c5 --- /dev/null +++ b/test/spec/modules/adikteevBidAdapter_spec.js @@ -0,0 +1,235 @@ +import {expect} from 'chai'; +import { + ENDPOINT_URL, + ENDPOINT_URL_STAGING, + setstagingEnvironmentSwitch, + spec, + stagingEnvironmentSwitch, + USER_SYNC_IFRAME_URL, + USER_SYNC_IFRAME_URL_STAGING, + USER_SYNC_IMAGE_URL, + USER_SYNC_IMAGE_URL_STAGING, +} from 'modules/adikteevBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../../../src/utils'; + +describe('adikteevBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(setstagingEnvironmentSwitch).to.exist.and.to.be.a('function'); + }); + it('exists and is correctly set', () => { + expect(stagingEnvironmentSwitch).to.exist.and.to.equal(false); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const validBid = { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + }; + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should mutate stagingEnvironmentSwitch when required params found', () => { + const withstagingEnvironmentSwitch = { + params: { + stagingEnvironment: true, + }, + }; + spec.isBidRequestValid(withstagingEnvironmentSwitch); + expect(stagingEnvironmentSwitch).to.equal(true); + setstagingEnvironmentSwitch(false); + }); + + it('should return false when required params are invalid', () => { + expect(spec.isBidRequestValid({ + bidder: '', // invalid bidder + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: '', // invalid placementId + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750]] // invalid size + } + }, + })).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const validBidRequests = []; + const bidderRequest = {}; + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + it('creates a request object with correct method, url and data', () => { + expect(serverRequest).to.exist.and.have.all.keys( + 'method', + 'url', + 'data', + ); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(ENDPOINT_URL); + + let requestData = JSON.parse(serverRequest.data); + expect(requestData).to.exist.and.have.all.keys( + 'validBidRequests', + 'bidderRequest', + 'userAgent', + 'screen', + 'language', + 'cookies', + // 'refererInfo', + // 'currency', + 'prebidUpdateVersion', + ); + expect(requestData.validBidRequests).to.deep.equal(validBidRequests); + expect(requestData.bidderRequest).to.deep.equal(bidderRequest); + expect(requestData.userAgent).to.deep.equal(navigator.userAgent); + expect(requestData.screen.width).to.deep.equal(window.screen.width); + expect(requestData.screen.height).to.deep.equal(window.screen.height); + expect(requestData.language).to.deep.equal(navigator.language); + expect(requestData.prebidUpdateVersion).to.deep.equal('1.29.0'); + }); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + expect(serverRequest.url).to.equal(ENDPOINT_URL_STAGING); + setstagingEnvironmentSwitch(false); + }); + }); + + describe('interpretResponse', () => { + it('bid objects from response', () => { + const serverResponse = + { + body: [ + { + cpm: 1, + width: 300, + height: 250, + ad: '
', + ttl: 360, + creativeId: 123, + netRevenue: false, + currency: 'EUR', + } + ] + }; + const payload = { + validBidRequests: [{ + bidId: '2ef7bb021ac847' + }], + }; + const bidRequests = { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + const bidResponses = spec.interpretResponse(serverResponse, bidRequests); + expect(bidResponses).to.be.an('array').that.is.not.empty; // yes, syntax is correct + expect(bidResponses[0]).to.have.all.keys( + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + ); + + expect(bidResponses[0].requestId).to.equal(payload.validBidRequests[0].bidId); + expect(bidResponses[0].cpm).to.equal(serverResponse.body[0].cpm); + expect(bidResponses[0].width).to.equal(serverResponse.body[0].width); + expect(bidResponses[0].height).to.equal(serverResponse.body[0].height); + expect(bidResponses[0].ad).to.equal(serverResponse.body[0].ad); + expect(bidResponses[0].ttl).to.equal(serverResponse.body[0].ttl); + expect(bidResponses[0].creativeId).to.equal(serverResponse.body[0].creativeId); + expect(bidResponses[0].netRevenue).to.equal(serverResponse.body[0].netRevenue); + expect(bidResponses[0].currency).to.equal(serverResponse.body[0].currency); + }); + }); + + describe('getUserSyncs', () => { + expect(spec.getUserSyncs({ + iframeEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }]); + + expect(spec.getUserSyncs({ + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL_STAGING + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL_STAGING + }]); + setstagingEnvironmentSwitch(false); + }); + }); +}); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9be87ac8628..5e41c1c9544 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -561,5 +561,15 @@ describe('AppNexusAdapter', function () { bidderRequest.bids[0].renderer.options ); }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + + let bidderRequest; + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + }); }); }); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js new file mode 100644 index 00000000000..dd34582e22d --- /dev/null +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -0,0 +1,338 @@ +import { expect } from 'chai'; +import { spec } from 'modules/emx_digitalBidAdapter'; +import * as utils from 'src/utils'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('emx_digital Adapter', function () { + const adapter = newBidder(spec); + + describe('required function', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should contain tagid param', function () { + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + tagid: '' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + tagid: '123' + } + })).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec' + }, { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec' + }]; + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + }; + bidderRequest.bids = bidRequests + + let request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('contains the correct options', function () { + expect(request.options.withCredentials).to.equal(true); + }); + + it('sends contains a properly formatted endpoint url', function () { + const url = request.url.split('?'); + const queryParams = url[1].split('&'); + expect(queryParams[0]).to.match(new RegExp('^t=\d*', 'g')); + expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); + }); + + it('builds request properly', function () { + const data = JSON.parse(request.data); + + expect(Array.isArray(data.imp)).to.equal(true); + expect(data.id).to.equal(bidderRequest.auctionId); + expect(data.imp.length).to.equal(2); + expect(data.imp[0].id).to.equal('30b31c2501de1e'); + expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ec'); + expect(data.imp[0].tagid).to.equal('25251'); + expect(data.imp[0].secure).to.equal(0); + }); + + it('builds with bid floor', function() { + const bidRequestWithBidFloor = utils.deepClone(bidRequests); + bidRequestWithBidFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + const data = JSON.parse(requestWithFloor.data); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + }) + + it('properly sends site information and protocol', function () { + let mock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => { + return { + protocol: 'https:', + host: 'example.com', + href: 'https://example.com/index.html' + }; + }); + + let request; + + request = spec.buildRequests(bidRequests, bidderRequest); + request = JSON.parse(request.data); + expect(request.site.domain).to.equal('example.com'); + expect(request.site.page).to.equal('https://example.com/index.html'); + expect(request.imp[0].secure).to.equal(1); + + mock.restore(); + }) + + it('builds correctly formatted request banner object', function () { + let request; + + let bidRequestWithBanner = utils.deepClone(bidRequests); + + request = spec.buildRequests(bidRequestWithBanner, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[0].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.format[0].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[1].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][0]); + expect(data.imp[0].banner.format[1].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][1]); + }) + + it('shouldn\'t contain a user obj without GDPR information', function () { + let request = spec.buildRequests(bidRequests, bidderRequest) + request = JSON.parse(request.data) + expect(request).to.not.have.property('user'); + }); + + it('should have the right gdpr info when enabled', function () { + let consentString = 'OIJSZsOAFsABAB8EMXZZZZZ+A=='; + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + }; + bidderRequest.bids = bidRequests + let request = spec.buildRequests(bidRequests, bidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 1); + expect(request.user.ext).to.have.property('consent', 'OIJSZsOAFsABAB8EMXZZZZZ+A=='); + }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'gdprConsent': { + 'gdprApplies': false + } + }; + bidderRequest.bids = bidRequests + let request = spec.buildRequests(bidRequests, bidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 0); + expect(request).to.not.have.property('user'); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + 'id': '12819a18-56e1-4256-b836-b69a10202668', + 'seatbid': [{ + 'bid': [{ + 'adid': '123456abcde', + 'adm': '', + 'crid': '3434abab34', + 'h': 250, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }], + 'seat': '1356' + }, { + 'bid': [{ + 'adid': '123456abcdf', + 'adm': '', + 'crid': '3434abab35', + 'h': 600, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }] + }] + }; + + const expectedResponse = [{ + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'creativeId': '3434abab34', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }, { + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.7, + 'width': 300, + 'height': 600, + 'creativeId': '3434abab35', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }]; + + it('should properly format bid response', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Object.keys(result[0]).length).to.equal(Object.keys(expectedResponse[0]).length); + expect(Object.keys(result[0]).requestId).to.equal(Object.keys(expectedResponse[0]).requestId); + expect(Object.keys(result[0]).bidderCode).to.equal(Object.keys(expectedResponse[0]).bidderCode); + expect(Object.keys(result[0]).cpm).to.equal(Object.keys(expectedResponse[0]).cpm); + expect(Object.keys(result[0]).creativeId).to.equal(Object.keys(expectedResponse[0]).creativeId); + expect(Object.keys(result[0]).width).to.equal(Object.keys(expectedResponse[0]).width); + expect(Object.keys(result[0]).height).to.equal(Object.keys(expectedResponse[0]).height); + expect(Object.keys(result[0]).ttl).to.equal(Object.keys(expectedResponse[0]).ttl); + expect(Object.keys(result[0]).adId).to.equal(Object.keys(expectedResponse[0]).adId); + expect(Object.keys(result[0]).currency).to.equal(Object.keys(expectedResponse[0]).currency); + expect(Object.keys(result[0]).netRevenue).to.equal(Object.keys(expectedResponse[0]).netRevenue); + expect(Object.keys(result[0]).ad).to.equal(Object.keys(expectedResponse[0]).ad); + }); + + it('should return multiple bids', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Array.isArray(result.seatbid)) + + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.ad).to.equal(serverResponse.seatbid[0].bid[0].adm); + expect(ad0.cpm).to.equal(serverResponse.seatbid[0].bid[0].price); + expect(ad0.creativeId).to.equal(serverResponse.seatbid[0].bid[0].crid); + expect(ad0.currency).to.equal('USD'); + expect(ad0.height).to.equal(serverResponse.seatbid[0].bid[0].h); + expect(ad0.mediaType).to.equal('banner'); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.requestId).to.equal(serverResponse.seatbid[0].bid[0].id); + expect(ad0.ttl).to.equal(300); + expect(ad0.width).to.equal(serverResponse.seatbid[0].bid[0].w); + + expect(ad1.ad).to.equal(serverResponse.seatbid[1].bid[0].adm); + expect(ad1.cpm).to.equal(serverResponse.seatbid[1].bid[0].price); + expect(ad1.creativeId).to.equal(serverResponse.seatbid[1].bid[0].crid); + expect(ad1.currency).to.equal('USD'); + expect(ad1.height).to.equal(serverResponse.seatbid[1].bid[0].h); + expect(ad1.mediaType).to.equal('banner'); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.requestId).to.equal(serverResponse.seatbid[1].bid[0].id); + expect(ad1.ttl).to.equal(300); + expect(ad1.width).to.equal(serverResponse.seatbid[1].bid[0].w); + }); + + it('handles nobid responses', function () { + let serverResponse = { + 'bids': [] + }; + + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js new file mode 100644 index 00000000000..f4401dfe677 --- /dev/null +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -0,0 +1,299 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gridBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TheMediaGrid Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + let bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + 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('auids', '1'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', function () { + 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('auids', '1,2'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 3, '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', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + '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': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + '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': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 2, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + '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', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '3' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '5' + }, + '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/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index 3c1048143d2..8167c29e5c2 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,7 +1,19 @@ import { expect } from 'chai' -import { spec } from 'modules/justpremiumBidAdapter' +import { spec, pixel } from 'modules/justpremiumBidAdapter' describe('justpremium adapter', function () { + let sandbox; + let pixelStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + pixelStub = sandbox.stub(pixel, 'fire'); + }); + + afterEach(function() { + sandbox.restore(); + }); + let adUnits = [ { adUnitCode: 'div-gpt-ad-1471513102552-1', @@ -132,7 +144,7 @@ describe('justpremium adapter', function () { }) describe('onTimeout', function () { - it('onTimeout', (done) => { + it('onTimeout', function(done) { spec.onTimeout([{ 'bidId': '25cd3ec3fd6ed7', 'bidder': 'justpremium', @@ -153,7 +165,9 @@ describe('justpremium adapter', function () { 'zone': 21521 }], 'timeout': 1 - }]) + }]); + + expect(pixelStub.calledOnce).to.equal(true); done() }) diff --git a/test/spec/modules/openxoutstreamBidAdapter_spec.js b/test/spec/modules/openxoutstreamBidAdapter_spec.js index 59d56bee74b..9b189856e1a 100644 --- a/test/spec/modules/openxoutstreamBidAdapter_spec.js +++ b/test/spec/modules/openxoutstreamBidAdapter_spec.js @@ -44,28 +44,23 @@ describe('OpenXOutstreamAdapter', function () { bannerBid.params = {delDomain: 'test-delivery-domain'} }); - it('should return false when there is no ad unit id and size', function () { + it('should return false if there is no adunit id and sizes are defined', function () { + bannerBid.mediaTypes.banner.sizes = [720, 90]; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); }); - it('should return true if there is an adunit id ', function () { + it('should return true if there is delivery domain and unit', function () { bannerBid.params.unit = '12345678'; expect(spec.isBidRequestValid(bannerBid)).to.equal(true); }); - - it('should return true if there is no adunit id and sizes are defined', function () { - bannerBid.mediaTypes.banner.sizes = [720, 90]; - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - }); - - it('should return false if no sizes are defined ', function () { + it('should return false if there is unit but no delivery domain', function () { + bannerBid.params = {unit: '12345678'}; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); }); - - it('should return false if sizes empty ', function () { - bannerBid.mediaTypes.banner.sizes = []; + it('shoud return false if there is no delivery domain and no unit', function () { + bannerBid.params = {}; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); + }) }); }); }); @@ -92,40 +87,45 @@ describe('OpenXOutstreamAdapter', function () { expect(request[0].method).to.equal('GET'); }); - it('should send ad unit ids when any are defined', function () { + it('should send ad unit ids, height, and width when any are defined', function () { const bidRequestsWithUnitIds = [{ 'bidder': BIDDER, 'params': { + 'unit': '540141567', + 'height': '300', + 'width': '250', 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', - 'sizes': [300, 250], + sizes: [300, 250], mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [[728, 90]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + 'bidId': 'test-bid-id-2', + 'bidderRequestId': 'test-bid-request-2', + 'auctionId': 'test-auction-2' }, { 'bidder': BIDDER, 'params': { - 'unit': '540141567', 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', + 'sizes': [300, 250], mediaTypes: { banner: { - sizes: [[728, 90]] + sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' }]; const request = spec.buildRequests(bidRequestsWithUnitIds); - expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[1].params.unit}`); + expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[0].params.unit}`); + expect(request[0].data.vht).to.equal(`${bidRequestsWithUnitIds[0].params.height}`); + expect(request[0].data.vwd).to.equal(`${bidRequestsWithUnitIds[0].params.width}`); }); describe('interpretResponse', function () { @@ -147,9 +147,9 @@ describe('OpenXOutstreamAdapter', function () { }; serverRequest = { payload: { - bids: [{ + bid: { bidId: '2d36ac90d654af', - }], + }, } }; }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index f5a7602c7ab..e64294e87e2 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -1,5 +1,6 @@ import * as utils from 'src/utils'; import { expect } from 'chai'; +import { stub, sandbox } from 'sinon'; import { QUANTCAST_DOMAIN, QUANTCAST_TEST_DOMAIN, @@ -12,6 +13,7 @@ import { } from '../../../modules/quantcastBidAdapter'; import { newBidder } from '../../../src/adapters/bidderFactory'; import { parse } from 'src/url'; +import * as ajax from 'src/ajax'; describe('Quantcast adapter', function () { const quantcastAdapter = newBidder(qcSpec); @@ -211,4 +213,19 @@ describe('Quantcast adapter', function () { expect(interpretedResponse.length).to.equal(0); }); }); + + describe('`onTimeout`', function() { + it('makes a request to the notify endpoint', function() { + const sinonSandbox = sandbox.create(); + const ajaxStub = sinonSandbox.stub(ajax, 'ajax').callsFake(function() {}); + const timeoutData = { + bidder: 'quantcast' + }; + qcSpec.onTimeout(timeoutData); + const expectedUrl = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; + ajaxStub.withArgs(expectedUrl, null, null).calledOnce.should.be.true; + ajaxStub.restore(); + sinonSandbox.restore(); + }); + }); }); diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index f903f6e9c2d..0fc20171e0a 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -9,6 +9,9 @@ import { dataLoaderForHandler, pinHandlerToHTMLElement, setAuctionAbjectPosition, + createNewAuctionObject, + concatAllUnits, + trackAuctionEnd, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; @@ -20,9 +23,11 @@ const events = require('../../../src/events'); describe('RIVR Analytics adapter', () => { const EXPIRING_QUEUE_TIMEOUT = 4000; const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; - const PUBLISHER_ID_MOCK = 777; + const RVR_CLIENT_ID_MOCK = 'aCliendId'; + const SITE_CATEGORIES_MOCK = ['cat1', 'cat2']; const EMITTED_AUCTION_ID = 1; const TRACKER_BASE_URL_MOCK = 'tracker.rivr.simplaex.com'; + const UUID_REG_EXP = new RegExp('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', 'i'); let sandbox; let ajaxStub; let timer; @@ -43,8 +48,9 @@ describe('RIVR Analytics adapter', () => { adaptermanager.enableAnalytics({ provider: 'rivr', options: { - pubId: PUBLISHER_ID_MOCK, - adUnits: [utils.deepClone(AD_UNITS_MOCK)] + clientID: RVR_CLIENT_ID_MOCK, + adUnits: [utils.deepClone(BANNER_AD_UNITS_MOCK)], + siteCategories: SITE_CATEGORIES_MOCK, } }); }); @@ -89,11 +95,15 @@ describe('RIVR Analytics adapter', () => { timer.tick(50); }); - it('enableAnalytics - should configure host and pubId in adapter context', () => { + it('enableAnalytics - should configure host and clientID in adapter context', () => { // adaptermanager.enableAnalytics() is called in beforeEach. If only called here it doesn't seem to work. expect(analyticsAdapter.context).to.have.property('host', TRACKER_BASE_URL_MOCK); - expect(analyticsAdapter.context).to.have.property('pubId', PUBLISHER_ID_MOCK); + expect(analyticsAdapter.context).to.have.property('clientID', RVR_CLIENT_ID_MOCK); + }); + + it('enableAnalytics - should set a cookie containing a user id', () => { + expect(UUID_REG_EXP.test(analyticsAdapter.context.userId)).to.equal(true); }); it('Firing AUCTION_INIT should set auction id of context when AUCTION_INIT event is fired', () => { @@ -138,79 +148,6 @@ describe('RIVR Analytics adapter', () => { expect(auctionObject3['modelVersion']).to.be.eql(null); }); - it('Firing BID_REQUESTED it sets app and site publisher id in auction object', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - - const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; - const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; - expect(sitePubcid).to.be.eql(PUBLISHER_ID_MOCK); - expect(appPubcid).to.be.eql(PUBLISHER_ID_MOCK); - }); - - it('Firing BID_REQUESTED it adds bid request in bid requests array', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - - const requestEvent = analyticsAdapter.context.auctionObject.bidRequests; - expect(requestEvent).to.have.length(1); - expect(requestEvent[0]).to.be.eql({ - bidderCode: 'adapter', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: 'adapter', - params: {}, - adUnitCode: 'container-1', - transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [[300, 250]], - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }); - }); - - it('Firing BID_RESPONSE it inserts bid response object in auctionObject', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - const bidResponses = analyticsAdapter.context.auctionObject.bidResponses; - - expect(bidResponses).to.have.length(1); - expect(bidResponses[0]).to.be.eql({ - timestamp: 1509369418832, - status: 1, - 'total_duration': 443, - bidderId: null, - 'bidder_name': 'adapter', - cur: 'EU', - seatbid: [ - { - seat: null, - bid: [ - { - status: 2, - 'clear_price': 0.015, - attr: [], - crid: 999, - cid: null, - id: null, - adid: '208750227436c1', - adomain: [], - iurl: null - } - ] - } - ] - }); - }); - it('Firing AUCTION_END it sets auction time end to current time', () => { analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); @@ -223,54 +160,28 @@ describe('RIVR Analytics adapter', () => { expect(endTime).to.be.eql(MILLIS_FROM_EPOCH_TO_NOW_MOCK); }); - it('Firing AUCTION_END when there are unresponded bid requests should insert then to bidResponses in auctionObject with null duration', () => { + it('Firing AUCTION_END populates impressions array in auction object', () => { analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST2); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST3); - events.emit(CONSTANTS.EVENTS.AUCTION_END, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); - const responses = analyticsAdapter.context.auctionObject.bidResponses; - expect(responses.length).to.be.eql(2); - expect(responses[0].total_duration).to.be.eql(null); - expect(responses[1].total_duration).to.be.eql(null); + const impressions = analyticsAdapter.context.auctionObject.impressions; + expect(impressions.length).to.be.eql(3); }); - it('Firing BID_WON when it happens after BID_RESPONSE should add won event as auction impression to imp array', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + it('Firing BID_WON should set to 1 the status of the corresponding bid', () => { + analyticsAdapter.context.auctionObject = utils.deepClone(AUCTION_OBJECT_AFTER_AUCTION_END_MOCK); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); - - const wonEvent = analyticsAdapter.context.auctionObject.imp; - - expect(wonEvent.length).to.be.eql(1); - expect(wonEvent[0]).to.be.eql({ - tagid: 'container-1', - displaymanager: null, - displaymanagerver: null, - secure: null, - bidfloor: null, - banner: { - w: 300, - h: 250, - pos: null, - expandable: [], - api: [] - } - }); - }); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_WON_MOCK); - it('Firing BID_WON when it happens after BID_RESPONSE should change the status of winning bidResponse to 1', () => { - const BID_STATUS_WON = 1; - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + expect(analyticsAdapter.context.auctionObject.bidders.length).to.be.equal(3); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + expect(analyticsAdapter.context.auctionObject.bidders[0].bids[0].status).to.be.equal(0); - const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; + expect(analyticsAdapter.context.auctionObject.bidders[1].bids[0].status).to.be.equal(0); - expect(responseWhichIsWonAlso.seatbid[0].bid[0].status).to.be.eql(BID_STATUS_WON); + expect(analyticsAdapter.context.auctionObject.bidders[2].bids[0].status).to.be.equal(1); + expect(analyticsAdapter.context.auctionObject.bidders[2].bids[1].status).to.be.equal(0); }); it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it sends the auction', () => { @@ -284,31 +195,23 @@ describe('RIVR Analytics adapter', () => { expect(ajaxStub.calledOnce).to.be.equal(true); }); - it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it clears imp, bidResponses and bidRequests', () => { + it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it resets auctionObject', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); analyticsAdapter.context.authToken = 'anAuthToken'; - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); - let impressions = analyticsAdapter.context.auctionObject.imp; - let responses = analyticsAdapter.context.auctionObject.bidResponses; - let requests = analyticsAdapter.context.auctionObject.bidRequests; + let impressions = analyticsAdapter.context.auctionObject.impressions; - expect(impressions.length).to.be.eql(1); - expect(responses.length).to.be.eql(1); - expect(requests.length).to.be.eql(1); + expect(impressions.length).to.be.eql(3); timer.tick(EXPIRING_QUEUE_TIMEOUT + 500); - let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; - let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; - let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; + let impressionsAfterSend = analyticsAdapter.context.auctionObject.impressions; + let biddersAfterSend = analyticsAdapter.context.auctionObject.bidders; expect(impressionsAfterSend.length).to.be.eql(0); - expect(responsesAfterSend.length).to.be.eql(0); - expect(requestsAfterSend.length).to.be.eql(0); + expect(biddersAfterSend.length).to.be.eql(0); }); it('sendAuction(), when authToken is defined, it fires call clearing empty payload properties', () => { @@ -547,7 +450,72 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.device.geo.lat).to.be.equal('aLatitude'); }); - const AD_UNITS_MOCK = [ + it('createNewAuctionObject(), it creates a new auction object', () => { + const MILLIS_FROM_EPOCH_TO_NOW_MOCK = 123456; + timer.tick(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + + const result = createNewAuctionObject(); + + expect(result.device.deviceType).to.be.equal(2); + expect(result.publisher).to.be.equal(RVR_CLIENT_ID_MOCK); + expect(result.device.userAgent).to.be.equal(navigator.userAgent); + expect(result.timestamp).to.be.equal(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + expect(result.site.domain.substring(0, 9)).to.be.equal('localhost'); + expect(result.site.page).to.be.equal('/context.html'); + expect(result.site.categories).to.be.equal(SITE_CATEGORIES_MOCK); + }); + + it('concatAllUnits(), returns a flattened array with all banner and video adunits', () => { + const allAdUnits = [BANNER_AD_UNITS_MOCK, VIDEO_AD_UNITS_MOCK]; + + const result = concatAllUnits(allAdUnits); + + expect(result.length).to.be.eql(2); + expect(result[0].code).to.be.eql('banner-container1'); + expect(result[1].code).to.be.eql('video'); + }); + + it('trackAuctionEnd(), populates the bidders array from bidderRequests and bidsReceived', () => { + trackAuctionEnd(AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK); + + const result = analyticsAdapter.context.auctionObject.bidders; + + expect(result.length).to.be.eql(3); + + expect(result[0].id).to.be.eql('vuble'); + expect(result[0].bids[0].price).to.be.eql(0); + + expect(result[1].id).to.be.eql('vertamedia'); + expect(result[1].bids[0].price).to.be.eql(0); + + expect(result[2].id).to.be.eql('appnexus'); + expect(result[2].bids[0].price).to.be.eql(0.5); + expect(result[2].bids[0].impId).to.be.eql('/19968336/header-bid-tag-0'); + expect(result[2].bids[1].price).to.be.eql(0.7); + expect(result[2].bids[1].impId).to.be.eql('/19968336/header-bid-tag-1'); + }); + + it('trackAuctionEnd(), populates the impressions array from adUnits', () => { + trackAuctionEnd(AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); + + const result = analyticsAdapter.context.auctionObject.impressions; + + expect(result.length).to.be.eql(3); + + expect(result[0].id).to.be.eql('/19968336/header-bid-tag-0'); + expect(result[0].adType).to.be.eql('banner'); + + expect(result[1].id).to.be.eql('/19968336/header-bid-tag-1'); + expect(result[1].adType).to.be.eql('banner'); + expect(result[1].acceptedSizes).to.be.eql([{w: 728, h: 90}, {w: 970, h: 250}]); + expect(result[1].banner).to.be.eql({w: 300, h: 250}); + + expect(result[2].id).to.be.eql('video'); + expect(result[2].adType).to.be.eql('video'); + expect(result[2].acceptedSizes).to.be.eql([{w: 640, h: 360}, {w: 640, h: 480}]); + }); + + const BANNER_AD_UNITS_MOCK = [ { code: 'banner-container1', mediaTypes: { @@ -573,6 +541,35 @@ describe('RIVR Analytics adapter', () => { } ]; + const VIDEO_AD_UNITS_MOCK = [ + { + code: 'video', + mediaTypes: { + video: { + context: 'outstream', + sizes: [[640, 360], [640, 480]] + } + }, + bids: [ + { + bidder: 'vuble', + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: 'http://www.vuble.tv/', // optional + floorPrice: 5.00 // optional + } + }, + { + bidder: 'vertamedia', + params: { + aid: 331133 + } + } + ] + }]; + const REQUEST = { bidderCode: 'adapter', auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', @@ -651,9 +648,56 @@ describe('RIVR Analytics adapter', () => { size: '300x250' }; + const BID_WON_MOCK = { + bidderCode: 'appnexus', + width: 300, + height: 600, + statusMessage: 'Bid available', + adId: '63301dc59deb3b', + mediaType: 'banner', + source: 'client', + requestId: '63301dc59deb3b', + cpm: 0.5, + creativeId: 98493581, + currency: 'USD', + netRevenue: true, + ttl: 300, + appnexus: { + buyerMemberId: 9325 + }, + ad: '...HTML CONTAINING THE AD...', + auctionId: '1825871c-b4c2-401a-b219-64549d412495', + responseTimestamp: 1540560447955, + requestTimestamp: 1540560447622, + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-0', + timeToRespond: 333, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '', + size: '300x600', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '63301dc59deb3b', + hb_pb: '0.50', + hb_size: '300x600', + hb_source: 'client', + hb_format: 'banner' + }, + status: 'rendered', + params: [ + { + placementId: 13144370 + } + ] + }; + const CONTEXT_AFTER_AUCTION_INIT = { host: TRACKER_BASE_URL_MOCK, - pubId: PUBLISHER_ID_MOCK, + clientID: RVR_CLIENT_ID_MOCK, queue: { mockProp: 'mockValue' }, @@ -699,4 +743,390 @@ describe('RIVR Analytics adapter', () => { 'ext.rivr.originalvalues': [] } }; + + const AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK = { + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart: 1540560217395, + auctionEnd: 1540560217703, + auctionStatus: 'completed', + adUnits: [ + { + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId: 'aee9bf8d-6d8f-425b-a42a-52c875371ebc', + sizes: [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + }, + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId: '3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', + sizes: [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + }, + { + code: 'video', + mediaTypes: { + video: { + context: 'outstream', + sizes: [ + [ + 640, + 360 + ], + [ + 640, + 480 + ] + ] + } + }, + bids: [ + { + bidder: 'vuble', + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: 'http: //www.vuble.tv/', + floorPrice: 5 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + }, + { + bidder: 'vertamedia', + params: { + aid: 331133 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d' + } + ], + adUnitCodes: [ + '/19968336/header-bid-tag-0', + '/19968336/header-bid-tag-1', + 'video' + ], + bidderRequests: [], + bidsReceived: [ + { + bidderCode: 'appnexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '6de82e80757293', + mediaType: 'banner', + source: 'client', + requestId: '6de82e80757293', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + netRevenue: true, + ttl: 300, + appnexus: { + buyerMemberId: 9325 + }, + ad: '...HTML CONTAINING THE AD...', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + responseTimestamp: 1540560217636, + requestTimestamp: 1540560217403, + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-1', + timeToRespond: 233, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '', + size: '728x90', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '7e1a45d85bd57c', + hb_pb: '0.50', + hb_size: '728x90', + hb_source: 'client', + hb_format: 'banner' + } + } + ], + winningBids: [], + timeout: 3000 + }; + + const AUCTION_OBJECT_AFTER_AUCTION_END_MOCK = { + bidders: [ + { + id: 'vuble', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: 'video', + price: 0, + status: 0 + } + ] + }, + { + id: 'vertamedia', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: 'video', + price: 0, + status: 0 + } + ] + }, + { + id: 'appnexus', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: '/19968336/header-bid-tag-0', + price: 0.5, + status: 0 + }, + { + adomain: [ + '' + ], + clearPrice: 0, + impId: '/19968336/header-bid-tag-1', + price: 0.7, + status: 0 + } + ] + } + ], + impressions: [] + }; + + const AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK = { + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart: 1540560217395, + auctionEnd: 1540560217703, + auctionStatus: 'completed', + adUnits: [], + adUnitCodes: [ + '/19968336/header-bid-tag-0', + '/19968336/header-bid-tag-1', + 'video' + ], + bidderRequests: [ + { + bidderCode: 'vuble', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '1bb11e055665bc', + bids: [ + { + bidder: 'vuble', + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: 'video', + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d', + bidId: '2859b890da7418', + bidderRequestId: '1bb11e055665bc', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + refererInfo: { + referer: 'http: //localhost: 8080/', + reachedTop: true, + numIframes: 0, + stack: [ + 'http://localhost:8080/' + ] + }, + start: 1540560217401, + doneCbCallCount: 0 + }, + { + bidderCode: 'vertamedia', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '3c2cbf7f1466cb', + bids: [ + { + bidder: 'vertamedia', + params: { + aid: 331133 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: 'video', + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d', + bidId: '45b3ad5c2dc794', + bidderRequestId: '3c2cbf7f1466cb', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + start: 1540560217401 + }, + { + bidderCode: 'appnexus', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '5312eef4418cd7', + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'aee9bf8d-6d8f-425b-a42a-52c875371ebc', + bidId: '6de82e80757293', + bidderRequestId: '5312eef4418cd7', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + }, + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', + bidId: '7e1a45d85bd57c', + bidderRequestId: '5312eef4418cd7', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + start: 1540560217403, + doneCbCallCount: 0 + } + ], + bidsReceived: [ + { + bidderCode: 'appnexus', + adId: '6de82e80757293', + mediaType: 'banner', + source: 'client', + requestId: '6de82e80757293', + cpm: 0.5, + creativeId: 96846035, + appnexus: { + buyerMemberId: 9325 + }, + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-0', + }, + { + bidderCode: 'appnexus', + adId: '7e1a45d85bd57c', + mediaType: 'banner', + source: 'client', + requestId: '7e1a45d85bd57c', + cpm: 0.7, + creativeId: 96846035, + appnexus: { + buyerMemberId: 9325 + }, + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-1', + } + ], + winningBids: [], + timeout: 3000 + }; }); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 3c1d81a86c9..bd341465ab9 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -52,17 +52,26 @@ describe('RTBHouseAdapter', () => { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + 'auctionId': '1d1a030790a475', + 'transactionId': 'example-transaction-id', } ]; + const bidderRequest = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } + }; it('should build test param into the request', () => { - let builtTestRequest = spec.buildRequests(bidRequests).data; + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; expect(JSON.parse(builtTestRequest).test).to.equal(1); }); it('should build valid OpenRTB banner object', () => { - const request = JSON.parse(spec.buildRequests((bidRequests)).data); + const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); const imp = request.imp[0]; expect(imp.banner).to.deep.equal({ w: 300, @@ -80,7 +89,7 @@ describe('RTBHouseAdapter', () => { it('sends bid request to ENDPOINT via POST', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebid/bids'); expect(request.method).to.equal('POST'); }); @@ -88,7 +97,7 @@ describe('RTBHouseAdapter', () => { it('should not populate GDPR if for non-EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); let data = JSON.parse(request.data); expect(data).to.not.have.property('regs'); expect(data).to.not.have.property('user'); @@ -97,12 +106,15 @@ describe('RTBHouseAdapter', () => { it('should populate GDPR and consent string if available for EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, { - gdprConsent: { - gdprApplies: true, - consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' - } - }); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ-A'); @@ -111,7 +123,14 @@ describe('RTBHouseAdapter', () => { it('should populate GDPR and empty consent string if available for EEA users without consent string but with consent', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, {gdprConsent: {gdprApplies: true}}); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal(''); @@ -119,11 +138,26 @@ describe('RTBHouseAdapter', () => { it('should include banner imp in request', () => { const bidRequest = Object.assign([], bidRequests); - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].banner).to.not.be.empty; }); + it('should include source.tid in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.tid).to.equal('example-transaction-id'); + }); + + it('should include bidfloor in request if available', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.bidfloor = 0.01; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].bidfloor).to.equal(0.01) + }); + describe('native imp', () => { function basicRequest(extension) { return Object.assign({ @@ -139,7 +173,8 @@ describe('RTBHouseAdapter', () => { } function buildImp(request) { - return JSON.parse(spec.buildRequests([request]).data).imp[0]; + const resultRequest = spec.buildRequests([request], bidderRequest); + return JSON.parse(resultRequest.data).imp[0]; } it('should extract native params when single mediaType', () => { diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index fa64513730a..97737675325 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -106,9 +106,59 @@ const MOCK = { [BID2.adUnitCode]: BID2.adserverTargeting }, AUCTION_INIT: { - 'timestamp': 1519767010567, 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timeout': 3000 + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag1'], + 'bidderRequests': [ { + 'bidderCode': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'auctionStart': 1519767010567, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000, + 'config': { + 'accountId': 1001, 'endpoint': '//localhost:9999/event' + } }, BID_REQUESTED: { 'bidder': 'rubicon', diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 4b816d851d9..400e86567ea 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -44,6 +44,12 @@ const bidReq = [{ auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874' }]; +const bidderReq = { + refererInfo: { + referer: 'http://prebid.org/dev-docs/bidder-adaptor.html' + } +}; + const validBidRes = { ad: '
Hello
', publisherId: 12345, @@ -98,14 +104,16 @@ describe('Undertone Adapter', function () { }); describe('build request', function () { it('should send request to correct url via POST', function () { - const request = spec.buildRequests(bidReq); - const domain = null; + const request = spec.buildRequests(bidReq, bidderReq); + const domainStart = bidderReq.refererInfo.referer.indexOf('//'); + const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.referer.substring(domainStart + 2, domainEnd); const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}`; expect(request.url).to.equal(REQ_URL); expect(request.method).to.equal('POST'); }); it('should have all relevant fields', function () { - const request = spec.buildRequests(bidReq); + const request = spec.buildRequests(bidReq, bidderReq); const bid1 = JSON.parse(request.data)['x-ut-hb-params'][0]; expect(bid1.bidRequestId).to.equal('263be71e91dd9d'); expect(bid1.sizes.length).to.equal(2); @@ -150,4 +158,27 @@ describe('Undertone Adapter', function () { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); }); + + describe('getUserSyncs', () => { + it('verifies gdpr consent checked', () => { + const options = ({ iframeEnabled: true, pixelEnabled: true }); + expect(spec.getUserSyncs(options, {}, { gdprApplies: true }).length).to.equal(0); + }); + + it('Verifies sync iframe option', function () { + const result = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }); + expect(result).to.have.lengthOf(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('//cdn.undertone.com/js/usersync.html'); + }); + + it('Verifies sync image option', function () { + const result = spec.getUserSyncs({ pixelEnabled: true }); + expect(result).to.have.lengthOf(2); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=1&of=2'); + expect(result[1].type).to.equal('image'); + expect(result[1].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=2&of=2'); + }); + }); }); diff --git a/test/spec/modules/uolBidAdapter_spec.js b/test/spec/modules/uolBidAdapter_spec.js index 1733afc91f9..e9341772e7d 100644 --- a/test/spec/modules/uolBidAdapter_spec.js +++ b/test/spec/modules/uolBidAdapter_spec.js @@ -1,11 +1,20 @@ import { expect } from 'chai'; import { spec } from 'modules/uolBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; const ENDPOINT = 'https://prebid.adilligo.com/v1/prebid.json'; describe('UOL Bid Adapter', function () { - const adapter = newBidder(spec); + let sandbox; + let queryStub; + let getCurrentPositionStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { @@ -88,31 +97,6 @@ describe('UOL Bid Adapter', function () { }); describe('buildRequests', function () { - let queryPermission; - let cleanup = function() { - navigator.permissions.query = queryPermission; - }; - let grantTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'granted'}); - }); - } - }; - let denyTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'prompt'}); - }); - } - }; - let removeQuerySupport = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = undefined; - } - let bidRequests = [ { 'bidder': 'uol', @@ -173,31 +157,42 @@ describe('UOL Bid Adapter', function () { describe('buildRequest geolocation param', function () { // shall only be tested if browser engine supports geolocation and permissions API. let geolocation = { lat: 4, long: 3, timestamp: 123121451 }; - it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function () { + beforeEach(function() { + getCurrentPositionStub = sandbox.stub(navigator.geolocation, 'getCurrentPosition'); + queryStub = sandbox.stub(navigator.permissions, 'query'); + }); + + it('should not contain user coordinates if browser doesnt support permission query', function () { localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - grantTriangulation(); + navigator.permissions.query = undefined; const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.exist.and.not.be.empty; - cleanup(); + expect(payload.geolocation).to.not.exist; }) - it('should not contain user coordinates if localStorage is empty', function () { - localStorage.removeItem('uolLocationTracker'); - denyTriangulation(); + it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function (done) { + localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'granted'}); + }); + }); + getCurrentPositionStub.callsFake(() => done()); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.not.exist; - cleanup(); + expect(payload.geolocation).to.exist.and.not.be.empty; }) - it('should not contain user coordinates if browser doesnt support permission query', function () { - localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - removeQuerySupport(); + it('should not contain user coordinates if localStorage is empty', function () { + localStorage.removeItem('uolLocationTracker'); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'prompt'}); + }); + }); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); expect(payload.geolocation).to.not.exist; - cleanup(); }) }) } diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index b717ef52709..19756b86bc1 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/yieldoneBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; const ENDPOINT = '//y.one.impact-ad.jp/h_bid'; +const USER_SYNC_URL = '//y.one.impact-ad.jp/push_sync'; describe('yieldoneBidAdapter', function() { const adapter = newBidder(spec); @@ -11,12 +12,10 @@ describe('yieldoneBidAdapter', function() { let bid = { 'bidder': 'yieldone', 'params': { - placementId: '44082' + placementId: '36891' }, 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250], [336, 280]], 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -43,12 +42,10 @@ describe('yieldoneBidAdapter', function() { { 'bidder': 'yieldone', 'params': { - placementId: '44082' + placementId: '36891' }, 'adUnitCode': 'adunit-code1', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250], [336, 280]], 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -56,12 +53,10 @@ describe('yieldoneBidAdapter', function() { { 'bidder': 'yieldone', 'params': { - placementId: '44337' + placementId: '47919' }, 'adUnitCode': 'adunit-code2', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250]], 'bidId': '382091349b149f"', 'bidderRequestId': '"1f9c98192de251"', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -79,18 +74,31 @@ describe('yieldoneBidAdapter', function() { expect(request[0].url).to.equal(ENDPOINT); expect(request[1].url).to.equal(ENDPOINT); }); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('300x250'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + const request = spec.buildRequests([bidRequest]); + expect(request[0].data.w).to.equal('300'); + expect(request[0].data.h).to.equal('250'); + }); }); describe('interpretResponse', function () { - let bidRequest = [ + let bidRequestBanner = [ { 'method': 'GET', 'url': '//y.one.impact-ad.jp/h_bid', 'data': { 'v': 'hb1', - 'p': '44082', - 'w': '300', - 'h': '250', + 'p': '36891', + 'sz': '300x250,336x280', 'cb': 12892917383, 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', 'uid': '23beaa6af6cdde', @@ -99,34 +107,89 @@ describe('yieldoneBidAdapter', function() { } ]; - let serverResponse = { + let serverResponseBanner = { body: { 'adTag': '', + 'uid': '23beaa6af6cdde', + 'height': 250, + 'width': 300, 'cpm': 0.0536616, 'crid': '2494768', + 'currency': 'JPY', 'statusMessage': 'Bid available', - 'uid': '23beaa6af6cdde', - 'width': 300, - 'height': 250 + 'dealId': 'P1-FIX-7800-DSP-MON' } }; - it('should get the correct bid response', function () { + it('should get the correct bid response for banner', function () { let expectedResponse = [{ 'requestId': '23beaa6af6cdde', 'cpm': 53.6616, 'width': 300, 'height': 250, 'creativeId': '2494768', - 'dealId': '', + 'dealId': 'P1-FIX-7800-DSP-MON', 'currency': 'JPY', 'netRevenue': true, 'ttl': 3000, 'referrer': '', + 'mediaType': 'banner', 'ad': '' }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + let result = spec.interpretResponse(serverResponseBanner, bidRequestBanner[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); + }); + + let serverResponseVideo = { + body: { + 'uid': '23beaa6af6cdde', + 'height': 360, + 'width': 640, + 'cpm': 0.0536616, + 'dealId': 'P1-FIX-766-DSP-MON', + 'crid': '2494768', + 'currency': 'JPY', + 'statusMessage': 'Bid available', + 'adm': '' + } + }; + + let bidRequestVideo = [ + { + 'method': 'GET', + 'url': '//y.one.impact-ad.jp/h_bid', + 'data': { + 'v': 'hb1', + 'p': '41993', + 'w': '640', + 'h': '360', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i' + } + } + ]; + + it('should get the correct bid response for video', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 53.6616, + 'width': 640, + 'height': 360, + 'creativeId': '2494768', + 'dealId': 'P1-FIX-7800-DSP-MON', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'mediaType': 'video', + 'vastXml': '' + }]; + let result = spec.interpretResponse(serverResponseVideo, bidRequestVideo[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); }); it('handles empty bid response', function () { @@ -140,14 +203,12 @@ describe('yieldoneBidAdapter', function() { 'cpm': 0 } }; - let result = spec.interpretResponse(response, bidRequest[0]); + let result = spec.interpretResponse(response, bidRequestBanner[0]); expect(result.length).to.equal(0); }); }); describe('getUserSyncs', function () { - const userSyncUrl = '//y.one.impact-ad.jp/push_sync'; - it('handles empty sync options', function () { expect(spec.getUserSyncs({})).to.be.empty; }); @@ -156,7 +217,7 @@ describe('yieldoneBidAdapter', function() { expect(spec.getUserSyncs({ 'iframeEnabled': true })).to.deep.equal([{ - type: 'iframe', url: userSyncUrl + type: 'iframe', url: USER_SYNC_URL }]); }); }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 91b96cac281..68653808c06 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -16,6 +16,19 @@ const bid = { } }; +const bidWithUndefinedFields = { + native: { + title: 'Native Creative', + body: undefined, + cta: undefined, + sponsoredBy: 'AppNexus', + clickUrl: 'https://www.link.example', + clickTrackers: ['https://tracker.example'], + impressionTrackers: ['https://impression.example'], + javascriptTrackers: '' + } +}; + describe('native.js', function () { let triggerPixelStub; let insertHtmlIntoIframeStub; @@ -37,6 +50,16 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); }); + it('should only include native targeting keys with values', function () { + const targeting = getNativeTargeting(bidWithUndefinedFields); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.sponsoredBy, + CONSTANTS.NATIVE_KEYS.clickUrl + ]); + }); + it('fires impression trackers', function () { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 9910645be09..427ceeca74c 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -99,42 +99,68 @@ const bid3 = { }; describe('targeting tests', function () { + let sandbox; + let enableSendAllBids = false; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'enableSendAllBids') { + return enableSendAllBids; + } + return origGetConfig.apply(config, arguments); + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('getAllTargeting', function () { let amBidsReceivedStub; let amGetAdUnitsStub; let bidExpiryStub; + let bidsReceived; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { - return [bid1, bid2, bid3]; + bidsReceived = [bid1, bid2, bid3]; + + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { + return bidsReceived; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - }); - - afterEach(function () { - auctionManager.getBidsReceived.restore(); - auctionManager.getAdUnitCodes.restore(); - targetingModule.isBidNotExpired.restore(); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); }); describe('when hb_deal is present in bid.adserverTargeting', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0; + enableSendAllBids = true; + + bidsReceived.push(bid4); + }); + it('returns targeting with both hb_deal and hb_deal_{bidder_code}', function () { const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // We should add both keys rather than one or the other - expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', `hb_deal_${bid1.bidderCode}`); + expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', `hb_deal_${bid1.bidderCode}`, `hb_deal_${bid4.bidderCode}`); // We should assign both keys the same value expect(targeting['/123456/header-bid-tag-0']['hb_deal']).to.deep.equal(targeting['/123456/header-bid-tag-0'][`hb_deal_${bid1.bidderCode}`]); }); }); - it('selects the top bid when _sendAllBids true', function () { - config.setConfig({ enableSendAllBids: true }); + it('selects the top bid when enableSendAllBids true', function () { + enableSendAllBids = true; let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // we should only get the targeting data for the one requested adunit @@ -155,14 +181,13 @@ describe('targeting tests', function () { let bidExpiryStub; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { return []; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); }); afterEach(function () { @@ -184,13 +209,8 @@ describe('targeting tests', function () { let bidExpiryStub; let auctionManagerStub; beforeEach(function () { - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - afterEach(function () { - bidExpiryStub.restore(); - auctionManagerStub.restore(); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); }); it('should use bids from pool to get Winning Bid', function () { @@ -248,14 +268,10 @@ describe('targeting tests', function () { let auctionManagerStub; let timestampStub; beforeEach(function () { - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - timestampStub = sinon.stub(utils, 'timestamp'); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); + timestampStub = sandbox.stub(utils, 'timestamp'); }); - afterEach(function () { - auctionManagerStub.restore(); - timestampStub.restore(); - }); it('should not include expired bids in the auction', function () { timestampStub.returns(200000); // Pool is having 4 bids from 2 auctions. All the bids are expired and only bid #3 is passing the bidExpiry check.