diff --git a/.travis.yml b/.travis.yml index 73b5de5ae4a..364d47edf4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: required + dist: trusty language: node_js diff --git a/pr_review.md b/PR_REVIEW.md similarity index 52% rename from pr_review.md rename to PR_REVIEW.md index 1d8cf0c360c..80d11c35769 100644 --- a/pr_review.md +++ b/PR_REVIEW.md @@ -1,5 +1,9 @@ ## Summary -We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Overall 1 person should take ownership of a particular PR. When they are satisfied it's in good condition to merge, they should request 1 additional team member to review as a sanity check. Only when the PR has 2 `LGTM` from the core team should it be merged. +We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Anyone in the community can review a PR, but a Prebid Org member is also required. A Prebid Org member should take ownership of a PR and do the initial review. + +If the PR is for a standard bid adapter or a standard analytics adapter, just the one review from a core member is sufficient. The reviewer will check against [required conventions](http://prebid.org/dev-docs/bidder-adaptor.html#required-adapter-conventions) and may merge the PR after approving and confirming that the documentation PR against prebid.org is open and linked to the issue. + +For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. ### General PR review Process - Checkout the branch (these instructions are available on the github PR page as well). @@ -13,7 +17,7 @@ We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-b - If all above is good, add a `LGTM` comment and request 1 additional core member to review. - Once there is 2 `LGTM` on the PR, merge to master - Ask the submitter to add a PR for documentation if applicable. -- Add a line into the `draft release` notes for this submission. If no draft release is available, create one using this template https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479 +- Add a line into the `draft release` notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) ### New Adapter or updates to adapter process - Follow steps above for general review process. In addition, please verify the following: @@ -23,3 +27,20 @@ We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-b - Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. - If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core. +- Requests to the bidder should support HTTPS +- Responses from the bidder should be compressed (such as gzip, compress, deflate) +- Bid responses may not use JSONP: All requests must be AJAX with JSON responses +- All user-sync (aka pixel) activity must be registered via the provided functions +- Adapters may not use the $$PREBID_GLOBAL$$ variable +- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. + +## Ticket Coordinator + +Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. That person should: +- Review issues and PRs at least once per weekday for new items. +- For PRs: assign PRs to individuals on the PR review list. Try to be equitable -- not all PRs are created equally. Use the "Assigned" field and add the "Needs Review" label. +- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed. +- Issues that are questions or troubleshooting requests may be closed if the originator doesn't respond within a week to requests for confirmation or details. +- Issues that are bug reports should be left open and assigned to someone in PR rotation to confirm or deny the bug status. +- It's polite to check with others before assigning them large tasks. +- If possible, check in on older items and see if they can be unstuck. diff --git a/integrationExamples/gpt/serverbidServer_example.html b/integrationExamples/gpt/serverbidServer_example.html new file mode 100644 index 00000000000..3d76e963663 --- /dev/null +++ b/integrationExamples/gpt/serverbidServer_example.html @@ -0,0 +1,103 @@ + + + + + + + + +

Prebid.js S2S Example

+ +
Div-1
+
+ +
+ + diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 8e73d5c87b4..5a7fa5e2613 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -16,7 +16,7 @@ function _createBidResponse(response) { height: response.seatbid[0].bid[0].h, ad: response.seatbid[0].bid[0].adm, ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].ext.rp.advid, + creativeId: response.seatbid[0].bid[0].crid, currency: response.cur, netRevenue: true } @@ -27,6 +27,9 @@ function _createServerRequest(bidRequest) { const ttxRequest = {}; const params = bidRequest.params; + /* + * Infer data for the request payload + */ ttxRequest.imp = []; ttxRequest.imp[0] = { banner: { @@ -38,22 +41,27 @@ function _createServerRequest(bidRequest) { } } } - ttxRequest.site = { id: params.siteId }; - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and // therefore in ad targetting process ttxRequest.id = bidRequest.bidId; + // Finally, set the openRTB 'test' param if this is to be a test bid + if (params.test === 1) { + ttxRequest.test = 1; + } + /* + * Now construt the full server request + */ const options = { contentType: 'application/json', withCredentials: false }; - // Allow the ability to configure the HB endpoint for testing purposes. const ttxSettings = config.getConfig('ttxSettings'); const url = (ttxSettings && ttxSettings.url) || END_POINT; + // Return the server request return { 'method': 'POST', 'url': url, @@ -93,12 +101,7 @@ function isBidRequestValid(bid) { return false; } - if ((typeof bid.params.site === 'undefined' || typeof bid.params.site.id === 'undefined') && - (typeof bid.params.siteId === 'undefined')) { - return false; - } - - if (typeof bid.params.productId === 'undefined') { + if (typeof bid.params.siteId === 'undefined' || typeof bid.params.productId === 'undefined') { return false; } diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 6dba55f88a7..4ff37566da9 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -5,7 +5,6 @@ import {registerBidder} from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'adform'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [], isBidRequestValid: function (bid) { return !!(bid.params.mid); }, @@ -35,7 +34,7 @@ export const spec = { request.unshift('//' + globalParams[0][1] + '/adx/?rp=4'); - request.push('stid=' + validBidRequests[0].requestId); + request.push('stid=' + validBidRequests[0].auctionId); for (i = 1, l = globalParams.length; i < l; i++) { _key = globalParams[i][0]; diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js new file mode 100644 index 00000000000..8635c684af7 --- /dev/null +++ b/modules/adgenerationBidAdapter.js @@ -0,0 +1,196 @@ +import * as utils from 'src/utils'; +// import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE } from 'src/mediaTypes'; +const ADG_BIDDER_CODE = 'adgeneration'; + +export const spec = { + code: ADG_BIDDER_CODE, + aliases: ['adg'], // short code + supportedMediaTypes: [BANNER, NATIVE], + /** + * 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.id); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + let serverRequests = []; + for (let i = 0, len = validBidRequests.length; i < len; i++) { + const validReq = validBidRequests[i]; + const DEBUG_URL = 'http://api-test.scaleout.jp/adsv/v1'; + const URL = 'https://d.socdm.com/adsv/v1'; + const url = validReq.params.debug ? DEBUG_URL : URL; + let data = ``; + data = utils.tryAppendQueryString(data, 'posall', 'SSPLOC'); + const id = utils.getBidIdParameter('id', validReq.params); + data = utils.tryAppendQueryString(data, 'id', id); + data = utils.tryAppendQueryString(data, 'sdktype', '0'); + data = utils.tryAppendQueryString(data, 'hb', 'true'); + data = utils.tryAppendQueryString(data, 't', 'json3'); + data = utils.tryAppendQueryString(data, 'transactionid', validReq.transactionId); + + // native以外にvideo等の対応が入った場合は要修正 + if (!validReq.mediaTypes || !validReq.mediaTypes.native) { + data = utils.tryAppendQueryString(data, 'imark', '1'); + } + + // remove the trailing "&" + if (data.lastIndexOf('&') === data.length - 1) { + data = data.substring(0, data.length - 1); + } + serverRequests.push({ + method: 'GET', + url: url, + data: data, + bidRequest: validBidRequests[i] + }); + } + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequests) { + const body = serverResponse.body; + const bidResponses = []; + let bidRequest = {}; + if (body.results && body.results.length > 0) { + bidRequest = bidRequests.bidRequest; + const bidResponse = { + requestId: bidRequest.bidId, + cpm: body.cpm || 0, + width: bidRequest.sizes[0][0], + height: bidRequest.sizes[0][1], + creativeId: body.creativeid || '', + dealId: body.dealid || '', + currency: 'JPY', + netRevenue: true, + ttl: body.ttl || 10, + referrer: utils.getTopWindowUrl(), + }; + if (bidRequest.mediaTypes && bidRequest.mediaTypes.native) { + bidResponse.native = createNativeAd(body); + bidResponse.mediaType = NATIVE; + } else { + // banner + bidResponse.ad = createAd(body, bidRequest); + } + bidResponses.push(bidResponse); + } + return bidResponses; // noAdは空を返す + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + return syncs; + } +}; + +function createAd(body, bidRequest) { + let ad = body.ad; + if (body.vastxml && body.vastxml.length > 0) { + ad = `
` + + appendChildToBody(ad, createAPVTag() + insertVASTMethod(bidRequest.bidId, body.vastxml)); + } + ad = appendChildToBody(ad, body.beacon); + if (removeWrapper(ad)) return removeWrapper(ad); + return ad; +} + +function createNativeAd(body) { + let native = {}; + if (body.native_ad && body.native_ad.assets.length > 0) { + const assets = body.native_ad.assets; + for (let i = 0, len = assets.length; i < len; i++) { + switch (assets[i].id) { + case 1: + native.title = assets[i].title.text; + break; + case 2: + native.image = { + url: assets[i].img.url, + height: assets[i].img.h, + width: assets[i].img.w, + }; + break; + case 3: + native.icon = { + url: assets[i].img.url, + height: assets[i].img.h, + width: assets[i].img.w, + }; + break; + case 4: + native.sponsoredBy = assets[i].data.value; + break; + case 5: + native.body = assets[i].data.value; + break; + case 6: + native.cta = assets[i].data.value; + break; + } + } + native.clickUrl = body.native_ad.link.url; + native.clickTrackers = body.native_ad.link.clicktrackers || []; + native.impressionTrackers = body.native_ad.imptrackers || []; + } + return native; +} + +function appendChildToBody(ad, data) { + return ad.replace(/<\/\s?body>/, `${data}`); +} + +function createAPVTag() { + const APVURL = 'https://cdn.apvdr.com/js/VideoAd.min.js'; + let apvScript = document.createElement('script'); + apvScript.type = 'text/javascript'; + apvScript.id = 'apv'; + apvScript.src = APVURL; + return apvScript.outerHTML; +} + +function insertVASTMethod(targetId, vastXml) { + let apvVideoAdParam = { + s: targetId + }; + let script = document.createElement(`script`); + script.type = 'text/javascript'; + script.innerHTML = `(function(){ new APV.VideoAd(${JSON.stringify(apvVideoAdParam)}).load('${vastXml.replace(/\r?\n/g, '')}'); })();`; + return script.outerHTML; +} + +/** + * + * @param ad + */ +function removeWrapper(ad) { + const bodyIndex = ad.indexOf(''); + const lastBodyIndex = ad.lastIndexOf(''); + if (bodyIndex === -1 || lastBodyIndex === -1) return false; + return ad.substr(bodyIndex, lastBodyIndex).replace('', '').replace('', ''); +} + +registerBidder(spec); diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md new file mode 100644 index 00000000000..d694b376ff9 --- /dev/null +++ b/modules/adgenerationBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Adgeneration Bid Adapter +Module Type: Bidder Adapter +Maintainer: ssp-ope@supership.jp +``` + +# Description + +Connects to Adgeneration exchange for bids. + +Adgeneration bid adapter supports Banner and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', // banner + sizes: [[300, 250]], + bids: [ + { + bidder: 'adg', + params: { + id: '58278', // banner + } + }, + ] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1,1]], + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + }, + }, + bids: [ + { + bidder: 'adg', + params: { + id: '58279', //native + } + }, + ] + }, +]; +``` diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 28fb564320d..c02a0cb138f 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -80,7 +80,7 @@ export const spec = { code: 'adkernelAdn', - supportedMediaTypes: [VIDEO], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bidRequest) { return 'params' in bidRequest && (typeof bidRequest.params.host === 'undefined' || typeof bidRequest.params.host === 'string') && diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 016cc5794fb..c3f34274692 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -16,7 +16,7 @@ export const spec = { code: 'adkernel', aliases: ['headbidding'], - supportedMediaTypes: [VIDEO], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bidRequest) { return 'params' in bidRequest && typeof bidRequest.params.host !== 'undefined' && 'zoneId' in bidRequest.params && !isNaN(Number(bidRequest.params.zoneId)); diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 64e3ae14835..30ef9c7dd90 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,6 +1,7 @@ import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; +import { logInfo } from 'src/utils'; import find from 'core-js/library/fn/array/find'; import findIndex from 'core-js/library/fn/array/find-index'; @@ -12,19 +13,14 @@ const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE; const bidWon = CONSTANTS.EVENTS.BID_WON; const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT; -let bidwonTimeout = 1000; - let adomikAdapter = Object.assign(adapter({}), { // Track every event needed track({ eventType, args }) { switch (eventType) { case auctionInit: + adomikAdapter.initializeBucketEvents() adomikAdapter.currentContext.id = args.auctionId - adomikAdapter.currentContext.timeout = args.timeout - if (args.config.bidwonTimeout !== undefined && typeof args.config.bidwonTimeout === 'number') { - bidwonTimeout = args.config.bidwonTimeout; - } break; case bidTimeout: @@ -39,12 +35,9 @@ let adomikAdapter = Object.assign(adapter({}), break; case bidWon: - adomikAdapter.bucketEvents.push({ - type: 'winner', - event: { - id: args.adId, - placementCode: args.adUnitCode - } + adomikAdapter.sendWonEvent({ + id: args.adId, + placementCode: args.adUnitCode }); break; @@ -61,24 +54,25 @@ let adomikAdapter = Object.assign(adapter({}), break; case auctionEnd: - setTimeout(() => { - if (adomikAdapter.bucketEvents.length > 0) { - adomikAdapter.sendTypedEvent(); - } - }, bidwonTimeout); + if (adomikAdapter.bucketEvents.length > 0) { + adomikAdapter.sendTypedEvent(); + } break; } } } ); +adomikAdapter.initializeBucketEvents = function() { + adomikAdapter.bucketEvents = []; +} + adomikAdapter.sendTypedEvent = function() { const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, - timeout: adomikAdapter.currentContext.timeout, hostname: window.location.hostname, eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { let sizes = []; @@ -108,8 +102,11 @@ adomikAdapter.sendTypedEvent = function() { }) }; + const stringBulkEvents = JSON.stringify(bulkEvents) + logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); + // Encode object in base64 - const encodedBuf = window.btoa(JSON.stringify(bulkEvents)); + const encodedBuf = window.btoa(stringBulkEvents); // Create final url and split it in 1600 characters max (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); @@ -122,6 +119,17 @@ adomikAdapter.sendTypedEvent = function() { }) }; +adomikAdapter.sendWonEvent = function (wonEvent) { + const stringWonEvent = JSON.stringify(wonEvent) + logInfo('Won event sent to adomik prebid analytic ' + wonEvent); + + // Encode object in base64 + const encodedBuf = window.btoa(stringWonEvent); + const encodedUri = encodeURIComponent(encodedBuf); + const img = new Image(1, 1); + img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true` +} + adomikAdapter.buildBidResponse = function (bid) { return { bidder: bid.bidderCode.toUpperCase(), @@ -181,23 +189,20 @@ adomikAdapter.buildTypedEvents = function () { return groupedTypedEvents; } -// Initialize adomik object -adomikAdapter.currentContext = {}; -adomikAdapter.bucketEvents = []; - adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; adomikAdapter.enableAnalytics = function (config) { + adomikAdapter.currentContext = {}; + const initOptions = config.options; if (initOptions) { adomikAdapter.currentContext = { uid: initOptions.id, url: initOptions.url, - debug: initOptions.debug, id: '', timeouted: false, - timeout: 0, } + logInfo('Adomik Analytics enabled with config', initOptions); adomikAdapter.adapterEnableAnalytics(config); } }; diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 44b1f381bbe..91bf0746afc 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils'; import * as url from 'src/url'; import {registerBidder} from 'src/adapters/bidderFactory'; -import {NATIVE, VIDEO} from 'src/mediaTypes'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes'; /** * Adapter for requesting bids from adxcg.net @@ -9,7 +9,7 @@ import {NATIVE, VIDEO} from 'src/mediaTypes'; */ const BIDDER_CODE = 'adxcg'; -const SUPPORTED_AD_TYPES = [VIDEO, NATIVE]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; const SOURCE = 'pbjs10'; export const spec = { code: BIDDER_CODE, diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 9b4aa26e1a1..35acb95cdee 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -345,7 +345,7 @@ export const spec = { let bidResponse = bidResponses[0]; if (config.getConfig('aol.userSyncOn') === constants.EVENTS.BID_RESPONSE) { - if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse.ext && bidResponse.ext.pixels) { + if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse && bidResponse.ext && bidResponse.ext.pixels) { $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; return parsePixelItems(bidResponse.ext.pixels); diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7db2b9aab2f..96163518a3a 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,13 +1,12 @@ import { Renderer } from 'src/Renderer'; import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; -import { NATIVE, VIDEO } from 'src/mediaTypes'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; const BIDDER_CODE = 'appnexus'; const URL = '//ib.adnxs.com/ut/v3/prebid'; -const SUPPORTED_AD_TYPES = ['banner', 'video', 'native']; const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks']; const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; @@ -30,8 +29,8 @@ const SOURCE = 'pbjs'; export const spec = { code: BIDDER_CODE, - aliases: ['appnexusAst', 'brealtime', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia'], - supportedMediaTypes: [VIDEO, NATIVE], + aliases: ['appnexusAst', 'brealtime', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia', 'districtm'], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. @@ -103,8 +102,8 @@ export const spec = { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(SUPPORTED_AD_TYPES, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid); + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); bid.mediaType = parseMediaType(rtbBid); bids.push(bid); } @@ -124,11 +123,11 @@ export const spec = { } } -function newRenderer(adUnitCode, rtbBid) { +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { const renderer = Renderer.install({ id: rtbBid.renderer_id, url: rtbBid.renderer_url, - config: { adText: `AppNexus Outstream Video Ad via Prebid.js` }, + config: rendererOptions, loaded: false, }); @@ -179,9 +178,10 @@ function getKeywords(keywords) { * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid * @param rtbBid + * @param bidderRequest * @return Bid */ -function newBid(serverBid, rtbBid) { +function newBid(serverBid, rtbBid, bidderRequest) { const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -197,21 +197,25 @@ function newBid(serverBid, rtbBid) { width: rtbBid.rtb.video.player_width, height: rtbBid.rtb.video.player_height, vastUrl: rtbBid.rtb.video.asset_url, - descriptionUrl: rtbBid.rtb.video.asset_url, ttl: 3600 }); // This supports Outstream Video if (rtbBid.renderer_url) { + const rendererOptions = utils.deepAccess( + bidderRequest.bids[0], + 'renderer.options' + ); + Object.assign(bid, { adResponse: serverBid, - renderer: newRenderer(bid.adUnitCode, rtbBid) + renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) }); bid.adResponse.ad = bid.adResponse.ads[0]; bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; } - } else if (rtbBid.rtb['native']) { - const nativeAd = rtbBid.rtb['native']; - bid['native'] = { + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + bid[NATIVE] = { title: nativeAd.title, body: nativeAd.desc, cta: nativeAd.ctatext, @@ -256,6 +260,7 @@ function bidToTag(bid) { const tag = {}; tag.sizes = transformSizes(bid.sizes); tag.primary_size = tag.sizes[0]; + tag.ad_types = []; tag.uuid = bid.bidId; if (bid.params.placementId) { tag.id = parseInt(bid.params.placementId, 10); @@ -294,19 +299,24 @@ function bidToTag(bid) { tag.keywords = getKeywords(bid.params.keywords); } - if (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) { - tag.ad_types = ['native']; + if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); if (bid.nativeParams) { const nativeRequest = buildNativeRequest(bid.nativeParams); - tag['native'] = {layouts: [nativeRequest]}; + tag[NATIVE] = {layouts: [nativeRequest]}; } } - const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - if (bid.mediaType === 'video' || (videoMediaType && context !== 'outstream')) { + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { tag.require_asset_url = true; } @@ -318,6 +328,13 @@ function bidToTag(bid) { .forEach(param => tag.video[param] = bid.params.video[param]); } + if ( + (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) + ) { + tag.ad_types.push(BANNER); + } + return tag; } @@ -414,12 +431,12 @@ function handleOutstreamRendererEvents(bid, id, eventName) { function parseMediaType(rtbBid) { const adType = rtbBid.ad_type; - if (adType === 'video') { - return 'video'; - } else if (adType === 'native') { - return 'native'; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; } else { - return 'banner'; + return BANNER; } } diff --git a/modules/atomxBidAdapter.js b/modules/atomxBidAdapter.js new file mode 100644 index 00000000000..f946841dffc --- /dev/null +++ b/modules/atomxBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'atomx'; + +function getDomain() { + var domain = ''; + + try { + if ((domain === '') && (window.top == window)) { + domain = window.location.href; + } + + if ((domain === '') && (window.top == window.parent)) { + domain = document.referrer; + } + + if (domain == '') { + var atomxt = 'atomxtest'; + + // It should be impossible to change the window.location.ancestorOrigins. + window.location.ancestorOrigins[0] = atomxt; + if (window.location.ancestorOrigins[0] != atomxt) { + var ancestorOrigins = window.location.ancestorOrigins; + + // If the length is 0 we are a javascript tag running in the main domain. + // But window.top != window or window.location.hostname is empty. + if (ancestorOrigins.length == 0) { + // This browser is so fucked up, just return an empty string. + return ''; + } + + // ancestorOrigins is an array where [0] is our own window.location + // and [length-1] is the top window.location. + domain = ancestorOrigins[ancestorOrigins.length - 1]; + } + } + } catch (unused) { + } + + if (domain === '') { + domain = document.referrer; + } + + if (domain === '') { + domain = window.location.href; + } + + return domain.substr(0, 512); +} + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return bid.params && (!!bid.params.id); + }, + + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + return { + method: 'GET', + url: location.protocol + '//p.ato.mx/placement', + data: { + v: 12, + id: bidRequest.params.id, + size: utils.parseSizesInput(bidRequest.sizes)[0], + prebid: bidRequest.bidId, + b: 0, + h: '7t3y9', + type: 'javascript', + screen: window.screen.width + 'x' + window.screen.height + 'x' + window.screen.colorDepth, + timezone: new Date().getTimezoneOffset(), + domain: getDomain(), + r: document.referrer.substr(0, 512), + }, + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const body = serverResponse.body; + const res = { + requestId: body.code, + cpm: body.cpm * 1000, + width: body.width, + height: body.height, + creativeId: body.creative_id, + currency: 'USD', + netRevenue: true, + ttl: 60, + }; + + if (body.adm) { + res.ad = body.adm; + } else { + res.adUrl = body.url; + } + + return [res]; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + return []; + }, +}; +registerBidder(spec); diff --git a/modules/atomxBidAdapter.md b/modules/atomxBidAdapter.md new file mode 100644 index 00000000000..7f32b12fdfe --- /dev/null +++ b/modules/atomxBidAdapter.md @@ -0,0 +1,25 @@ +# Overview +Module Name: Atomx Bidder Adapter Module +Type: Bidder Adapter +Maintainer: erik@atomx.com + +# Description +Atomx Bidder Adapter for Prebid.js. + +# Test Parameters +``` +var adUnits = [ +{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'atomx', + params: { + id: 4025860, + } + } + ] +} +]; +``` diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js index 41ca539ccba..3153a64a48d 100644 --- a/modules/audienceNetworkBidAdapter.js +++ b/modules/audienceNetworkBidAdapter.js @@ -12,7 +12,7 @@ const code = 'audienceNetwork'; const currency = 'USD'; const method = 'GET'; const url = 'https://an.facebook.com/v2/placementbid.json'; -const supportedMediaTypes = ['video']; +const supportedMediaTypes = ['banner', 'video']; const netRevenue = true; const hb_bidder = 'fan'; @@ -143,7 +143,7 @@ const buildRequests = bids => { }; const video = findIndex(adformats, isVideo); if (video !== -1) { - [search.playerwidth, search.playerheight] = sizes[video].split('x').map(Number) + [search.playerwidth, search.playerheight] = expandSize(sizes[video]); } const data = formatQS(search); @@ -162,51 +162,50 @@ const buildRequests = bids => { const interpretResponse = ({ body }, { adformats, requestIds, sizes }) => { const ttl = Number(config.getConfig().bidderTimeout); - return body.errors && body.errors.length - ? [] - : Object.keys(body.bids) - // extract Array of bid responses - .map(placementId => body.bids[placementId]) - // flatten - .reduce((a, b) => a.concat(b), []) - // transform to bidResponse - .map((bid, i) => { - const { - bid_id: fb_bidid, - placement_id: creativeId, - bid_price_cents: cpm - } = bid; - - const format = adformats[i]; - const [width, height] = expandSize(flattenSize(sizes[i])); - const ad = createAdHtml(creativeId, format, fb_bidid); - const requestId = requestIds[i]; - - const bidResponse = { - // Prebid attributes - requestId, - cpm: cpm / 100, - width, - height, - ad, - ttl, - creativeId, - netRevenue, - currency, - // Audience Network attributes - hb_bidder, - fb_bidid, - fb_format: format, - fb_placementid: creativeId - }; - // Video attributes - if (isVideo(format)) { - const pageurl = getTopWindowUrlEncoded(); - bidResponse.mediaType = 'video'; - bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fb_bidid}`; - } - return bidResponse; - }); + const { bids = {} } = body; + return Object.keys(bids) + // extract Array of bid responses + .map(placementId => bids[placementId]) + // flatten + .reduce((a, b) => a.concat(b), []) + // transform to bidResponse + .map((bid, i) => { + const { + bid_id: fb_bidid, + placement_id: creativeId, + bid_price_cents: cpm + } = bid; + + const format = adformats[i]; + const [width, height] = expandSize(flattenSize(sizes[i])); + const ad = createAdHtml(creativeId, format, fb_bidid); + const requestId = requestIds[i]; + + const bidResponse = { + // Prebid attributes + requestId, + cpm: cpm / 100, + width, + height, + ad, + ttl, + creativeId, + netRevenue, + currency, + // Audience Network attributes + hb_bidder, + fb_bidid, + fb_format: format, + fb_placementid: creativeId + }; + // Video attributes + if (isVideo(format)) { + const pageurl = getTopWindowUrlEncoded(); + bidResponse.mediaType = 'video'; + bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fb_bidid}`; + } + return bidResponse; + }); }; export const spec = { diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js new file mode 100644 index 00000000000..af1de373985 --- /dev/null +++ b/modules/c1xBidAdapter.js @@ -0,0 +1,167 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; +import { userSync } from 'src/userSync'; + +const BIDDER_CODE = 'c1x'; +const URL = 'https://ht.c1exchange.com/ht'; +const PIXEL_ENDPOINT = '//px.c1exchange.com/pubpixel/'; +const LOG_MSG = { + invalidBid: 'C1X: [ERROR] bidder returns an invalid bid', + noSite: 'C1X: [ERROR] no site id supplied', + noBid: 'C1X: [INFO] creating a NO bid for Adunit: ', + bidWin: 'C1X: [INFO] creating a bid for Adunit: ' +}; + +/** + * Adapter for requesting bids from C1X header tag server. + * v3.0 (c) C1X Inc., 2017 + */ + +export const c1xAdapter = { + code: BIDDER_CODE, + + // check the bids sent to c1x bidder + isBidRequestValid: function(bid) { + const siteId = bid.params.siteId || ''; + if (!siteId) { + utils.logError(LOG_MSG.noSite); + } + return !!(bid.adUnitCode && siteId); + }, + + buildRequests: function(bidRequests) { + let payload = {}; + let tagObj = {}; + const adunits = bidRequests.length; + const rnd = new Date().getTime(); + const c1xTags = bidRequests.map(bidToTag); + const bidIdTags = bidRequests.map(bidToShortTag); // include only adUnitCode and bidId from request obj + + // flattened tags in a tag object + tagObj = c1xTags.reduce((current, next) => Object.assign(current, next)); + const pixelId = tagObj.pixelId; + const useSSL = document.location.protocol; + + payload = { + adunits: adunits.toString(), + rnd: rnd.toString(), + response: 'json', + compress: 'gzip' + } + Object.assign(payload, tagObj); + + let payloadString = stringifyPayload(payload); + + if (pixelId) { + const pixelUrl = (useSSL ? 'https:' : 'http:') + PIXEL_ENDPOINT + pixelId; + userSync.registerSync('image', BIDDER_CODE, pixelUrl); + } + + // ServerRequest object + return { + method: 'GET', + url: URL, + data: payloadString, + bids: bidIdTags + }; + }, + + interpretResponse: function(serverResponse, requests) { + serverResponse = serverResponse.body; + requests = requests.bids || []; + const currency = 'USD'; + const bidResponses = []; + let netRevenue = false; + + if (!serverResponse || serverResponse.error) { + let errorMessage = serverResponse.error; + utils.logError(LOG_MSG.invalidBid + errorMessage); + return bidResponses; + } else { + serverResponse.forEach(bid => { + if (bid.bid) { + if (bid.bidType === 'NET_BID') { + netRevenue = !netRevenue; + } + const curBid = { + width: bid.width, + height: bid.height, + cpm: bid.cpm, + ad: bid.ad, + creativeId: bid.crid, + currency: currency, + ttl: 300, + netRevenue: netRevenue + }; + + for (let i = 0; i < requests.length; i++) { + if (bid.adId === requests[i].adUnitCode) { + curBid.requestId = requests[i].bidId; + } + } + utils.logInfo(LOG_MSG.bidWin + bid.adId + ' size: ' + curBid.width + 'x' + curBid.height); + bidResponses.push(curBid); + } else { + // no bid + utils.logInfo(LOG_MSG.noBid + bid.adId); + } + }); + } + + return bidResponses; + } +} + +function bidToTag(bid, index) { + const tag = {}; + const adIndex = 'a' + (index + 1).toString(); // ad unit id for c1x + const sizeKey = adIndex + 's'; + const priceKey = adIndex + 'p'; + // TODO: Multiple Floor Prices + + const sizesArr = bid.sizes; + const floorPriceMap = bid.params.floorPriceMap || ''; + tag['site'] = bid.params.siteId || ''; + + // prevent pixelId becoming undefined when publishers don't fill this param in ad units they have on the same page + if (bid.params.pixelId) { + tag['pixelId'] = bid.params.pixelId + } + + tag[adIndex] = bid.adUnitCode; + tag[sizeKey] = sizesArr.reduce((prev, current) => prev + (prev === '' ? '' : ',') + current.join('x'), ''); + + const newSizeArr = tag[sizeKey].split(','); + if (floorPriceMap) { + newSizeArr.forEach(size => { + if (size in floorPriceMap) { + tag[priceKey] = floorPriceMap[size].toString(); + } // we only accept one cpm price in floorPriceMap + }); + } + if (bid.params.pageurl) { + tag['pageurl'] = bid.params.pageurl; + } + + return tag; +} + +function bidToShortTag(bid) { + const tag = {}; + tag.adUnitCode = bid.adUnitCode; + tag.bidId = bid.bidId; + + return tag; +} + +function stringifyPayload(payload) { + let payloadString = ''; + payloadString = JSON.stringify(payload).replace(/":"|","|{"|"}/g, (foundChar) => { + if (foundChar == '":"') return '='; + else if (foundChar == '","') return '&'; + else return ''; + }); + return payloadString; +} + +registerBidder(c1xAdapter); diff --git a/modules/c1xBidAdapter.md b/modules/c1xBidAdapter.md new file mode 100644 index 00000000000..83a4ff1ea81 --- /dev/null +++ b/modules/c1xBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: C1X Bidder Adapter +Module Type: Bidder Adapter +Maintainer: cathy@c1exchange.com + +# Description + +Module that connects to C1X's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 600], [300, 250]], + bids: [ + { + bidder: 'c1x', + params: { + siteId: '9999', + pixelId: '12345', + floorPriceMap: { + '300x250': 0.20, + '300x600': 0.30 + }, //optional + } + } + ] + }, + ]; +``` \ No newline at end of file diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js new file mode 100644 index 00000000000..df011bc102d --- /dev/null +++ b/modules/colossussspBidAdapter.js @@ -0,0 +1,132 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +const BIDDER_CODE = 'colossusssp'; +const URL = '//colossusssp.com/?c=o&m=multi'; +const URL_SYNC = '//colossusssp.com/?c=o&m=cookie'; + +let sizeObj = { + '468x60': 1, + '728x90': 2, + '300x600': 10, + '300x250': 15, + '300x100': 19, + '320x50': 43, + '300x50': 44, + '300x300': 48, + '300x1050': 54, + '970x90': 55, + '970x250': 57, + '1000x90': 58, + '320x80': 59, + '640x480': 65, + '320x480': 67, + '320x320': 72, + '320x160': 73, + '480x300': 83, + '970x310': 94, + '970x210': 96, + '480x320': 101, + '768x1024': 102, + '1000x300': 113, + '320x100': 117, + '800x250': 118, + '200x600': 119 +}; + +utils._each(sizeObj, (item, key) => sizeObj[item] = key); + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return (!isNaN(bid.params.placement_id) && + ((bid.params.sizes !== undefined && bid.params.sizes.length > 0 && bid.params.sizes.some((sizeIndex) => sizeObj[sizeIndex] !== undefined)) || + (bid.sizes !== undefined && bid.sizes.length > 0 && bid.sizes.map((size) => `${size[0]}x${size[1]}`).some((size) => sizeObj[size] !== undefined)))); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests) => { + let winTop = window; + try { + window.top.location.toString(); + winTop = window.top; + } catch (e) { + utils.logMessage(e); + }; + let location = utils.getTopWindowLocation(); + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': location.protocol === 'https:' ? 1 : 0, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + for (let i = 0; i < validBidRequests.length; i++) { + let bid = validBidRequests[i]; + let placement = {}; + placement['placementId'] = bid.params.placement_id; + placement['bidId'] = bid.bidId; + placement['sizes'] = bid.sizes; + placements.push(placement); + } + return { + method: 'POST', + url: URL, + data: request + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + let response = []; + try { + serverResponse = serverResponse.body; + for (let i = 0; i < serverResponse.length; i++) { + let resItem = serverResponse[i]; + if (resItem.width && !isNaN(resItem.width) && + resItem.height && !isNaN(resItem.height) && + resItem.requestId && typeof resItem.requestId === 'string' && + resItem.cpm && !isNaN(resItem.cpm) && + resItem.ad && typeof resItem.ad === 'string' && + resItem.ttl && !isNaN(resItem.ttl) && + resItem.creativeId && typeof resItem.creativeId === 'string' && + resItem.netRevenue && typeof resItem.netRevenue === 'boolean' && + resItem.currency && typeof resItem.currency === 'string') { + response.push(resItem); + } + } + } catch (e) { + utils.logMessage(e); + }; + return response; + }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: URL_SYNC + }]; + } +}; + +registerBidder(spec); diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md new file mode 100644 index 00000000000..9a5b9a0fe39 --- /dev/null +++ b/modules/colossussspBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Colossus SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@colossusmediallc.com +``` + +# Description + +Module that connects to Colossus SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementid_0', + sizes: [[300, 250]], + bids: [{ + bidder: 'colossusssp', + params: { + placement_id: 0 + } + }] + } + ]; +``` diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 1f6c4f27b77..cae64983089 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; -import { VIDEO } from 'src/mediaTypes'; +import { BANNER, VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'conversant'; const URL = '//media.msg.dotomi.com/s2s/header/24'; @@ -10,7 +10,7 @@ const VERSION = '2.2.1'; export const spec = { code: BIDDER_CODE, aliases: ['cnvr'], // short code - supportedMediaTypes: [VIDEO], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. diff --git a/modules/danmarketplaceBidAdapter.js b/modules/danmarketplaceBidAdapter.js new file mode 100644 index 00000000000..24b3682042e --- /dev/null +++ b/modules/danmarketplaceBidAdapter.js @@ -0,0 +1,143 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'danmarketplace'; +const ENDPOINT_URL = '//ads.danmarketplace.com/hb'; +const TIME_TO_LIVE = 360; +const ADAPTER_SYNC_URL = '//ads.danmarketplace.com/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +/** + * Dentsu Aegis Network Marketplace Bid Adapter. + * Contact: niels@baarsma.net + * + */ +export const spec = { + code: BIDDER_CODE, + + aliases: ['DANMarketplace', 'DAN_Marketplace'], + + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + reqId = bid.bidderRequestId; + }); + + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + r: reqId, + }; + + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidsMap: bidsMap, + }; + }, + + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; + } + } +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/danmarketplaceBidAdapter.md b/modules/danmarketplaceBidAdapter.md new file mode 100755 index 00000000000..263385949bd --- /dev/null +++ b/modules/danmarketplaceBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Dentsu Aegis Network Marketplace Bidder Adapter +Module Type: Bidder Adapter +Maintainer: niels@baarsma.net + +# Description + +Module that connects to DAN Marketplace demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "danmarketplace", + params: { + uid: '4', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "danmarketplace", + params: { + uid: 5, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js new file mode 100644 index 00000000000..dfc5f514cf3 --- /dev/null +++ b/modules/eplanningBidAdapter.js @@ -0,0 +1,156 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'eplanning'; +const rnd = Math.random(); +const DEFAULT_SV = 'ads.us.e-planning.net'; +const DEFAULT_ISV = 'i.e-planning.net'; +const PARAMS = ['ci', 'sv', 't']; +const DOLLARS = 'USD'; +const NET_REVENUE = true; +const TTL = 120; +const NULL_SIZE = '1x1'; +const FILE = 'file'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return Boolean(bid.params.ci) || Boolean(bid.params.t); + }, + buildRequests: function(bidRequests) { + const method = 'GET'; + const dfpClientId = '1'; + const sec = 'ROS'; + let url; + let params; + const urlConfig = getUrlConfig(bidRequests); + + if (urlConfig.t) { + url = urlConfig.isv + '/layers/t_pbjs_2.json'; + params = {}; + } else { + url = '//' + (urlConfig.sv || DEFAULT_SV) + '/hb/1/' + urlConfig.ci + '/' + dfpClientId + '/' + (utils.getTopWindowLocation().hostname || FILE) + '/' + sec; + const referrerUrl = utils.getTopWindowReferrer(); + const spacesString = getSpacesString(bidRequests); + params = { + rnd: rnd, + e: spacesString, + ur: utils.getTopWindowUrl() || FILE, + r: 'pbjs', + pbv: '$prebid.version$', + ncb: '1' + }; + if (referrerUrl) { + params.fr = referrerUrl; + } + } + + return { + method: method, + url: url, + data: params, + adUnitToBidId: getBidIdMap(bidRequests), + }; + }, + interpretResponse: function(serverResponse, request) { + const response = serverResponse.body; + let bidResponses = []; + + if (response && !utils.isEmpty(response.sp)) { + response.sp.forEach(space => { + if (!utils.isEmpty(space.a)) { + space.a.forEach(ad => { + const bidResponse = { + requestId: request.adUnitToBidId[space.k], + cpm: ad.pr, + width: ad.w, + height: ad.h, + ad: ad.adm, + ttl: TTL, + creativeId: ad.crid, + netRevenue: NET_REVENUE, + currency: DOLLARS, + }; + bidResponses.push(bidResponse); + }); + } + }); + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + const response = !utils.isEmpty(serverResponses) && serverResponses[0].body; + + if (response && !utils.isEmpty(response.cs)) { + const responseSyncs = response.cs; + responseSyncs.forEach(sync => { + if (typeof sync === 'string' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: sync, + }); + } else if (typeof sync === 'object' && sync.ifr && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: sync.u, + }) + } + }); + } + + return syncs; + }, +} + +function cleanName(name) { + return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)/g, '_').replace(/^_+|_+$/g, ''); +} +function getUrlConfig(bidRequests) { + if (isTestRequest(bidRequests)) { + return getTestConfig(bidRequests.filter(br => br.params.t)); + } + + let config = {}; + bidRequests.forEach(bid => { + PARAMS.forEach(param => { + if (bid.params[param] && !config[param]) { + config[param] = bid.params[param]; + } + }); + }); + + if (config.sv) { + config.sv = '//' + config.sv; + } + + return config; +} +function isTestRequest(bidRequests) { + let isTest = false; + bidRequests.forEach(bid => isTest = bid.params.t); + return isTest; +} +function getTestConfig(bidRequests) { + let isv; + bidRequests.forEach(br => isv = isv || br.params.isv); + return { + t: true, + isv: '//' + (isv || DEFAULT_ISV) + }; +} +function getSpacesString(bids) { + const spacesString = bids.map(bid => + cleanName(bid.adUnitCode) + ':' + (bid.sizes && bid.sizes.length ? utils.parseSizesInput(bid.sizes).join(',') : NULL_SIZE) + ).join('+'); + + return spacesString; +} +function getBidIdMap(bidRequests) { + let map = {}; + bidRequests.forEach(bid => map[cleanName(bid.adUnitCode)] = bid.bidId); + return map; +} + +registerBidder(spec); diff --git a/modules/eplanningBidAdapter.md b/modules/eplanningBidAdapter.md new file mode 100644 index 00000000000..b6cfbb535b6 --- /dev/null +++ b/modules/eplanningBidAdapter.md @@ -0,0 +1,25 @@ +# Overview + +``` +Module Name: E-Planning Bid Adapter +Module Type: Bidder Adapter +Maintainer: ainsua@e-planning.net +``` + +# Description + +Connects to E-Planning exchange for bids. + +# Test Parameters +``` +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250]], + bids: [{ + bidder: 'eplanning', + params: { + t: 1 + } + }] +}]; +``` diff --git a/modules/freewheelSSPBidAdapter.js b/modules/freewheelSSPBidAdapter.js index 64ebb36478b..632df8fe93c 100644 --- a/modules/freewheelSSPBidAdapter.js +++ b/modules/freewheelSSPBidAdapter.js @@ -190,7 +190,7 @@ var getOutstreamScript = function(bid) { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: ['video'], + supportedMediaTypes: ['banner', 'video'], aliases: ['stickyadstv'], // former name for freewheel-ssp /** * Determines whether or not the given bid request is valid. diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js new file mode 100644 index 00000000000..2627b7417f5 --- /dev/null +++ b/modules/gumgumBidAdapter.js @@ -0,0 +1,170 @@ +import * as utils from 'src/utils' + +import { config } from 'src/config' +import { registerBidder } from 'src/adapters/bidderFactory' + +const BIDDER_CODE = 'gumgum' +const ALIAS_BIDDER_CODE = ['gg'] +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const DT_CREDENTIALS = { member: 'YcXr87z2lpbB' } +const TIME_TO_LIVE = 60 +let browserParams = {}; + +// TODO: potential 0 values for browserParams sent to ad server +function _getBrowserParams() { + let topWindow + let topScreen + if (browserParams.vw) { + // we've already initialized browserParams, just return it. + return browserParams + } + + try { + topWindow = global.top; + topScreen = topWindow.screen; + } catch (error) { + utils.logError(error); + return browserParams + } + + browserParams = { + vw: topWindow.innerWidth, + vh: topWindow.innerHeight, + sw: topScreen.width, + sh: topScreen.height, + pu: utils.getTopWindowUrl(), + ce: utils.cookiesAreEnabled(), + dpr: topWindow.devicePixelRatio || 1 + } + return browserParams +} + +function getWrapperCode(wrapper, data) { + return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) +} + +// TODO: use getConfig() +function _getDigiTrustQueryParams() { + function getDigiTrustId () { + var digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(DT_CREDENTIALS) : {}; + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || ''; + }; + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return {}; + } + return { + 'dt': digiTrustId.id + }; +} + +/** + * 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. + */ +function isBidRequestValid (bid) { + const { + params, + adUnitCode + } = bid; + + switch (true) { + case !!(params.inScreen): break; + case !!(params.inSlot): break; + default: + utils.logWarn(`[GumGum] No product selected for the placement ${adUnitCode}, please check your implementation.`); + return false; + } + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests (validBidRequests) { + const bids = []; + utils._each(validBidRequests, bidRequest => { + const timeout = config.getConfig('bidderTimeout'); + const { + bidId, + params = {}, + transactionId + } = bidRequest; + const data = {} + + if (params.inScreen) { + data.t = params.inScreen; + data.pi = 2; + } + if (params.inSlot) { + data.si = parseInt(params.inSlot, 10); + data.pi = 3; + } + + bids.push({ + id: bidId, + tmax: timeout, + tId: transactionId, + pi: data.pi, + sizes: bidRequest.sizes, + url: BID_ENDPOINT, + method: 'GET', + data: Object.assign(data, _getBrowserParams(), _getDigiTrustQueryParams()) + }) + }); + return bids; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse (serverResponse, bidRequest) { + const bidResponses = [] + const serverResponseBody = serverResponse.body + const { + ad: { + price: cpm, + id: creativeId, + markup + }, + cw: wrapper + } = serverResponseBody + let isTestUnit = (bidRequest.data && bidRequest.data.pi === 3 && bidRequest.data.si === 9) + let [width, height] = utils.parseSizesInput(bidRequest.sizes)[0].split('x') + + if (creativeId) { + bidResponses.push({ + // dealId: DEAL_ID, + // referrer: REFERER, + ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, + cpm: isTestUnit ? 0.1 : cpm, + creativeId, + currency: 'USD', + height, + netRevenue: true, + requestId: bidRequest.id, + ttl: TIME_TO_LIVE, + width + }) + } + return bidResponses +} + +export const spec = { + code: BIDDER_CODE, + aliases: ALIAS_BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse +} +registerBidder(spec) diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md new file mode 100644 index 00000000000..500c2a49e3b --- /dev/null +++ b/modules/gumgumBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: GumGum Bidder Adapter +Module Type: Bidder Adapter +Maintainer: engineering@gumgum.com +``` + +# Description + +GumGum adapter for Prebid.js 1.0 + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'gumgum', + params: { + inSlot: '9' // GumGum Slot ID given to the client + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: 'gumgum', + params: { + inScreen: 'ggumtest' // GumGum Zone ID given to the client + } + } + ] + } +]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 82c045b9db6..2dcdcb2a808 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,11 +1,12 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; import { userSync } from 'src/userSync'; const BIDDER_CODE = 'improvedigital'; export const spec = { - version: '4.0.0', + version: '4.1.0', code: BIDDER_CODE, aliases: ['id'], @@ -113,6 +114,7 @@ function getNormalizedBidRequest(bid) { let localSize = utils.getBidIdParameter('size', bid.params) || null; let bidId = utils.getBidIdParameter('bidId', bid); let transactionId = utils.getBidIdParameter('transactionId', bid); + const currency = config.getConfig('currency.adServerCurrency'); let normalizedBidRequest = {}; if (placementId) { @@ -143,6 +145,9 @@ function getNormalizedBidRequest(bid) { if (transactionId) { normalizedBidRequest.transactionId = transactionId; } + if (currency) { + normalizedBidRequest.currency = currency; + } return normalizedBidRequest; } registerBidder(spec); @@ -160,7 +165,7 @@ function ImproveDigitalAdServerJSClient(endPoint) { AD_SERVER_BASE_URL: 'ad.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-4.2.0', + CLIENT_VERSION: 'JS-4.3.3', MAX_URL_LENGTH: 2083, ERROR_CODES: { BAD_HTTP_REQUEST_TYPE_PARAM: 1, @@ -337,6 +342,9 @@ function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.adUnitId) { outputObject.adUnitId = placementObject.adUnitId; } + if (placementObject.currency) { + impressionObject.currency = placementObject.currency.toUpperCase(); + } if (placementObject.placementId) { impressionObject.pid = placementObject.placementId; } diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index 3d91d4f82f2..d70b624171f 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -12,6 +12,7 @@ Module that connects to Improve Digital's demand sources ``` var adUnits = [{ code: 'div-gpt-ad-1499748733608-0', + sizes: [[600, 290]], bids: [ { bidder: 'improvedigital', @@ -22,6 +23,7 @@ Module that connects to Improve Digital's demand sources ] }, { code: 'div-gpt-ad-1499748833901-0', + sizes: [[250, 250]], bids: [{ bidder: 'improvedigital', params: { @@ -33,6 +35,7 @@ Module that connects to Improve Digital's demand sources }] }, { code: 'div-gpt-ad-1499748913322-0', + sizes: [[300, 300]], bids: [{ bidder: 'improvedigital', params: { diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js new file mode 100644 index 00000000000..5274b5b2ba8 --- /dev/null +++ b/modules/inskinBidAdapter.js @@ -0,0 +1,210 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'inskin'; + +const CONFIG = { + 'inskin': { + 'BASE_URI': 'https://mfad.inskinad.com/api/v2' + } +}; + +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.networkId && bid.params.siteId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + + buildRequests: function(validBidRequests) { + // Do we need to group by bidder? i.e. to make multiple requests for + // different endpoints. + + let ret = { + method: 'POST', + url: '', + data: '', + bidRequest: [] + }; + + if (validBidRequests.length < 1) { + return ret; + } + + let ENDPOINT_URL; + + const data = Object.assign({ + placements: [], + time: Date.now(), + user: {}, + url: utils.getTopWindowUrl(), + referrer: document.referrer, + enableBotFiltering: true, + includePricingData: true, + parallel: true + }, validBidRequests[0].params); + + validBidRequests.map(bid => { + let config = CONFIG[bid.bidder]; + ENDPOINT_URL = config.BASE_URI; + + const placement = Object.assign({ + divName: bid.bidId, + adTypes: bid.adTypes || getSize(bid.sizes), + eventIds: [40, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295] + }, bid.params); + + placement.adTypes.push(5, 9, 163, 2163, 3006); + + if (placement.networkId && placement.siteId) { + data.placements.push(placement); + } + }); + + ret.data = JSON.stringify(data); + ret.bidRequest = validBidRequests; + ret.url = ENDPOINT_URL; + + return ret; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bid; + let bids; + let bidId; + let bidObj; + let bidResponses = []; + let bidsMap = {}; + + bids = bidRequest.bidRequest; + + serverResponse = (serverResponse || {}).body; + for (let i = 0; i < bids.length; i++) { + bid = {}; + bidObj = bids[i]; + bidId = bidObj.bidId; + + bidsMap[bidId] = bidObj; + + if (serverResponse) { + const decision = serverResponse.decisions && serverResponse.decisions[bidId]; + const price = decision && decision.pricing && decision.pricing.clearPrice; + + if (decision && price) { + bid.requestId = bidId; + bid.cpm = price; + bid.width = decision.width; + bid.height = decision.height; + bid.ad = retrieveAd(bidId, decision); + bid.currency = 'USD'; + bid.creativeId = decision.adId; + bid.ttl = 360; + bid.netRevenue = true; + bid.referrer = utils.getTopWindowUrl(); + + bidResponses.push(bid); + } + } + } + + if (bidResponses.length) { + window.addEventListener('message', function(e) { + if (!e.data || e.data.from !== 'ism-bid') { + return; + } + + const decision = serverResponse.decisions && serverResponse.decisions[e.data.bidId]; + if (!decision) { + return; + } + + const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); + window[id] = { + bidId: e.data.bidId, + serverResponse + }; + const script = document.createElement('script'); + script.src = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; + document.getElementsByTagName('head')[0].appendChild(script); + }); + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + return []; + } +}; + +const sizeMap = [ + null, + '120x90', + '120x90', + '468x60', + '728x90', + '300x250', + '160x600', + '120x600', + '300x100', + '180x150', + '336x280', + '240x400', + '234x60', + '88x31', + '120x60', + '120x240', + '125x125', + '220x250', + '250x250', + '250x90', + '0x0', + '200x90', + '300x50', + '320x50', + '320x480', + '185x185', + '620x45', + '300x125', + '800x250' +]; + +sizeMap[77] = '970x90'; +sizeMap[123] = '970x250'; +sizeMap[43] = '300x600'; + +function getSize(sizes) { + const result = []; + sizes.forEach(function(size) { + const index = sizeMap.indexOf(size[0] + 'x' + size[1]); + if (index >= 0) { + result.push(index); + } + }); + return result; +} + +function retrieveAd(bidId, decision) { + return " + +``` \ No newline at end of file diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js index c4cce6646d3..225859a4360 100644 --- a/modules/nanointeractiveBidAdapter.js +++ b/modules/nanointeractiveBidAdapter.js @@ -3,28 +3,21 @@ import { registerBidder } from '../src/adapters/bidderFactory'; import { BANNER } from '../src/mediaTypes'; export const BIDDER_CODE = 'nanointeractive'; -export const ENGINE_BASE_URL = 'http://tmp.audiencemanager.de/hb'; +export const ENGINE_BASE_URL = 'https://www.audiencemanager.de/hb'; -export const SECURITY = 'sec'; -export const DATA_PARTNER_ID = 'dpid'; export const DATA_PARTNER_PIXEL_ID = 'pid'; -export const ALG = 'alg'; export const NQ = 'nq'; export const NQ_NAME = 'name'; export const CATEGORY = 'category'; -const DEFAULT_ALG = 'ihr'; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], isBidRequestValid(bid) { - const sec = bid.params[SECURITY]; - const dpid = bid.params[DATA_PARTNER_ID]; const pid = bid.params[DATA_PARTNER_PIXEL_ID]; - return !!(sec && dpid && pid); + return !!(pid); }, buildRequests(bidRequests) { let payload = []; @@ -37,7 +30,7 @@ export const spec = { }, interpretResponse(serverResponse) { const bids = []; - serverResponse.forEach(serverBid => { + serverResponse.body.forEach(serverBid => { if (isEngineResponseValid(serverBid)) { bids.push(createSingleBidResponse(serverBid)); } @@ -48,10 +41,7 @@ export const spec = { function createSingleBidRequest(bid) { return { - [SECURITY]: bid.params[SECURITY], - [DATA_PARTNER_ID]: bid.params[DATA_PARTNER_ID], [DATA_PARTNER_PIXEL_ID]: bid.params[DATA_PARTNER_PIXEL_ID], - [ALG]: bid.params[ALG] || DEFAULT_ALG, [NQ]: [createNqParam(bid), createCategoryParam(bid)], sizes: bid.sizes.map(value => value[0] + 'x' + value[1]), bidId: bid.bidId, diff --git a/modules/nanointeractiveBidAdapter.md b/modules/nanointeractiveBidAdapter.md index 0159353750a..0df49999492 100644 --- a/modules/nanointeractiveBidAdapter.md +++ b/modules/nanointeractiveBidAdapter.md @@ -31,8 +31,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163' } }] @@ -44,8 +42,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163', nq: 'user search' } @@ -58,8 +54,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163', name: 'search' } diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 051f9ace2de..9ed4f1250f8 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -30,15 +30,21 @@ export const spec = { let requests = []; let bannerRequests = []; let videoRequests = []; - let bannerBids = []; - let videoBids = []; - bids.forEach(function (bid) { - if (bid.mediaType === VIDEO) { - videoBids.push(bid); - } else { - bannerBids.push(bid); + const {bannerBids, videoBids} = bids.reduce(function(acc, curBid) { + // Fallback to banner ads if nothing specified + if (!curBid.mediaTypes || utils.isEmpty(curBid.mediaTypes)) { + if (curBid.mediaType && curBid.mediaType == VIDEO) { + acc.videoBids.push(curBid); + } else { + acc.bannerBids.push(curBid); + } + } else if (curBid.mediaTypes.video) { + acc.videoBids.push(curBid); + } else if (curBid.mediaTypes.banner) { + acc.bannerBids.push(curBid); } - }); + return acc; + }, {bannerBids: [], videoBids: []}); // build banner requests if (bannerBids.length !== 0) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 1f04c2fe466..9e9d3ebfa7a 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -16,13 +16,13 @@ Module that connects to OpenX's demand sources { code: 'test-div', sizes: [[728, 90]], // a display size - mediaType: 'banner', + mediaTypes: {'banner': {}}, bids: [ { - bidder: "openx", + bidder: 'openx', params: { - unit: "539439964", - delDomain: "se-demo-d.openx.net" + unit: '539439964', + delDomain: 'se-demo-d.openx.net' } } ] @@ -30,7 +30,7 @@ Module that connects to OpenX's demand sources { code: 'video1', sizes: [[640,480]], - mediaType: 'video', + mediaTypes: {'video': {}}, bids: [ { bidder: 'openx', diff --git a/modules/optimaticBidAdapter.js b/modules/optimaticBidAdapter.js index 2408eb8cefe..f406fe7828d 100644 --- a/modules/optimaticBidAdapter.js +++ b/modules/optimaticBidAdapter.js @@ -3,6 +3,8 @@ import { registerBidder } from 'src/adapters/bidderFactory'; export const ENDPOINT = '//mg-bid.optimatic.com/adrequest/'; export const spec = { + version: '1.0.4', + code: 'optimatic', supportedMediaTypes: ['video'], @@ -33,7 +35,7 @@ export const spec = { } catch (e) { response = null; } - if (!response || !bid || !bid.adm || !bid.price) { + if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { utils.logWarn(`No valid bids from ${spec.code} bidder`); return []; } @@ -43,7 +45,6 @@ export const spec = { bidderCode: spec.code, cpm: bid.price, creativeId: bid.id, - vastXml: bid.adm, width: size.width, height: size.height, mediaType: 'video', @@ -51,6 +52,11 @@ export const spec = { ttl: 300, netRevenue: true }; + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } else if (bid.adm) { + bidResponse.vastXml = bid.adm; + } return bidResponse; } }; diff --git a/modules/optimeraBidAdapter.js b/modules/optimeraBidAdapter.js new file mode 100644 index 00000000000..fc3c4c96f1b --- /dev/null +++ b/modules/optimeraBidAdapter.js @@ -0,0 +1,84 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'optimera'; +const SCORES_BASE_URL = 'https://s3.amazonaws.com/elasticbeanstalk-us-east-1-397719490216/json/client/'; + +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 (bidRequest) { + if (typeof bidRequest.params !== 'undefined' && typeof bidRequest.params.clientID !== 'undefined') { + return true; + } else { + return false; + } + }, + /** + * Make a server request from the list of BidRequests. + * + * We call the existing scores data file for ad slot placement scores. + * These scores will be added to the dealId to be pushed to DFP. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + let optimeraHost = window.location.host; + let optimeraPathName = window.location.pathname; + let timestamp = Math.round(new Date().getTime() / 1000); + if (typeof validBidRequests[0].params.clientID !== 'undefined') { + let clientID = validBidRequests[0].params.clientID; + let scoresURL = SCORES_BASE_URL + clientID + '/' + optimeraHost + optimeraPathName + '.js'; + return { + method: 'GET', + url: scoresURL, + payload: validBidRequests, + data: {'t': timestamp} + }; + } + }, + /** + * Unpack the response from the server into a list of bids. + * + * Some required bid params are not needed for this so default + * values are used. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let validBids = bidRequest.payload; + let bidResponses = []; + let dealId = ''; + if (typeof serverResponse.body !== 'undefined') { + let scores = serverResponse.body; + for (let i = 0; i < validBids.length; i++) { + if (typeof validBids[i].params.clientID !== 'undefined') { + if (validBids[i].adUnitCode in scores) { + dealId = scores[validBids[i].adUnitCode]; + } + let bidResponse = { + requestId: validBids[i].bidId, + ad: '
', + cpm: 0.01, + width: 0, + height: 0, + dealId: dealId, + ttl: 300, + creativeId: '1', + netRevenue: '0', + currency: 'USD' + }; + bidResponses.push(bidResponse); + } + } + } + return bidResponses; + } +} + +registerBidder(spec); diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md new file mode 100644 index 00000000000..8fca42fdac0 --- /dev/null +++ b/modules/optimeraBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Optimera Bidder Adapter +Module Type: Bidder Adapter +Maintainer: kcandiotti@optimera.nyc +``` + +# Description + +Module that adds ad placement visibility scores for DFP. + +# Test Parameters +``` + var adUnits = [{ + code: 'div-1', + sizes: [[300, 250], [300,600]], + bids: [ + { + bidder: 'optimera', + params: { + clientID: '0' + } + }] + },{ + code: 'div-0', + sizes: [[728, 90]], + bids: [ + { + bidder: 'optimera', + params: { + clientID: '0' + } + }] + }]; +``` diff --git a/modules/peak226BidAdapter.js b/modules/peak226BidAdapter.js new file mode 100644 index 00000000000..4f4ee2f97ff --- /dev/null +++ b/modules/peak226BidAdapter.js @@ -0,0 +1,97 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; +import { getTopWindowUrl, logWarn } from 'src/utils'; + +const BIDDER_CODE = 'peak226'; +const URL = '//a.ad216.com/header_bid'; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + const { params } = bid; + + return !!params.uid; + }, + + buildRequests: function (validBidRequests) { + const bidsMap = validBidRequests.reduce((res, bid) => { + const { uid } = bid.params; + + res[uid] = res[uid] || []; + res[uid].push(bid); + + return res; + }, {}); + + return { + method: 'GET', + url: + URL + + toQueryString({ + u: getTopWindowUrl(), + auids: Object.keys(bidsMap).join(',') + }), + bidsMap + }; + }, + + interpretResponse: function (serverResponse, { bidsMap }) { + const response = serverResponse.body; + const bidResponses = []; + + if (!response) { + logWarn(`No response from ${spec.code} bidder`); + + return bidResponses; + } + + if (!response.seatbid || !response.seatbid.length) { + logWarn(`No seatbid in response from ${spec.code} bidder`); + + return bidResponses; + } + + response.seatbid.forEach((seatbid, i) => { + if (!seatbid.bid || !seatbid.bid.length) { + logWarn(`No bid in seatbid[${i}] response from ${spec.code} bidder`); + return; + } + seatbid.bid.forEach(responseBid => { + const requestBids = bidsMap[responseBid.auid]; + + requestBids.forEach(requestBid => { + bidResponses.push({ + requestId: requestBid.bidId, + bidderCode: spec.code, + width: responseBid.w, + height: responseBid.h, + mediaType: BANNER, + creativeId: responseBid.auid, + ad: responseBid.adm, + cpm: responseBid.price, + currency: 'USD', + netRevenue: true, + ttl: 360 + }); + }); + }); + }); + + return bidResponses; + } +}; + +function toQueryString(obj) { + return Object.keys(obj).reduce( + (str, key, i) => + typeof obj[key] === 'undefined' || obj[key] === '' + ? str + : `${str}${str ? '&' : '?'}${key}=${encodeURIComponent(obj[key])}`, + '' + ); +} + +registerBidder(spec); diff --git a/modules/peak226BidAdapter.md b/modules/peak226BidAdapter.md new file mode 100644 index 00000000000..bae15d6c99f --- /dev/null +++ b/modules/peak226BidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Peak226 Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@edge226.com +``` + +# Description + +Module that connects to Peak226's demand sources + +# Test Parameters + +``` + var adUnits = [ + { + code: "test-div", + sizes: [[300, 250]], + mediaType: "banner", + bids: [ + { + bidder: "peak226", + params: { + uid: 76131369 + } + } + ] + } + ]; +``` diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js index fa841dc6026..93907c9bb1a 100644 --- a/modules/platformioBidAdapter.js +++ b/modules/platformioBidAdapter.js @@ -1,10 +1,19 @@ -import {logError, getTopWindowLocation} from 'src/utils'; +import {logError, getTopWindowLocation, getTopWindowReferrer} from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +const NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; + export const spec = { code: 'platformio', + supportedMediaTypes: ['native'], isBidRequestValid: bid => ( !!(bid && bid.params && bid.params.pubId && bid.params.siteId) @@ -27,6 +36,7 @@ export const spec = { bidResponseAvailable(request, response.body) ), }; + function bidResponseAvailable(bidRequest, bidResponse) { const idToImpMap = {}; const idToBidMap = {}; @@ -44,19 +54,31 @@ function bidResponseAvailable(bidRequest, bidResponse) { if (idToBidMap[id]) { const bid = {}; bid.requestId = id; - bid.creativeId = idToBidMap[id].adid; + bid.adId = id; + bid.creativeId = id; bid.cpm = idToBidMap[id].price; bid.currency = bidResponse.cur; bid.ttl = 360; bid.netRevenue = true; - bid.ad = idToBidMap[id].adm; - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid.width = idToImpMap[id].banner.w; - bid.height = idToImpMap[id].banner.h; + if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + let nurl = idToBidMap[id].nurl; + nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid['native']['impressionTrackers'] = [nurl]; + bid.mediaType = 'native'; + } else { + bid.ad = idToBidMap[id].adm; + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + } bids.push(bid); } }); @@ -66,43 +88,95 @@ function impression(slot) { return { id: slot.bidId, banner: banner(slot), + 'native': nativeImpression(slot), bidfloor: slot.params.bidFloor || '0.000001', tagid: slot.params.placementId.toString(), }; } function banner(slot) { - const size = slot.params.size.toUpperCase().split('X'); - const width = parseInt(size[0]); - const height = parseInt(size[1]); - return { - w: width, - h: height, + if (!slot.nativeParams) { + const size = slot.params.size.toUpperCase().split('X'); + const width = parseInt(size[0]); + const height = parseInt(size[1]); + return { + w: width, + h: height, + }; }; } -function site(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; - const appParams = bidderRequest[0].params.app; - if (!appParams) { + +function nativeImpression(slot) { + if (slot.nativeParams) { + const assets = []; + addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); return { - publisher: { - id: pubId.toString(), - domain: getTopWindowLocation().hostname, + request: JSON.stringify({ assets }), + ver: '1.1', + }; + } + return null; +} + +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} + +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, }, - id: siteId.toString(), - ref: referrer(), - page: getTopWindowLocation().href, - } + }; } return null; } -function referrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; + +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + return { + publisher: { + id: pubId.toString(), + domain: getTopWindowLocation().hostname, + }, + id: siteId.toString(), + ref: getTopWindowReferrer(), + page: getTopWindowLocation().href, } } + function device() { return { ua: navigator.userAgent, @@ -122,4 +196,25 @@ function parse(rawResponse) { return null; } +function nativeResponse(imp, bid) { + if (imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { + nativeAd['native'].assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; + keys.icon = asset.img && asset.id === 4 ? asset.img.url : keys.icon; + keys.image = asset.img && asset.id === 5 ? asset.img.url : keys.image; + }); + if (nativeAd['native'].link) { + keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); + } + return keys; + } + } + return null; +} + registerBidder(spec); diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md index 0d019d1fe96..81ac81380b0 100644 --- a/modules/platformioBidAdapter.md +++ b/modules/platformioBidAdapter.md @@ -1,12 +1,13 @@ # Overview -**Module Name**: Platform.io Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: sk@ultralab.by +**Module Name**: Platform.io Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: siarhei.kasukhin@platform.io # Description -Connects to Platform.io demand source to fetch bids. +Connects to Platform.io demand source to fetch bids. +Banner and Native formats are supported. Please use ```platformio``` as the bidder code. # Test Parameters @@ -17,11 +18,30 @@ Please use ```platformio``` as the bidder code. bids: [{ bidder: 'platformio', params: { - pubId: '28082', // required - siteId: '26047', // required - size: '250X250', // required - placementId: '123', - bidFloor: '0.001' + pubId: '29521', // required + siteId: '26047', // required + size: '250X250', // required + placementId: '123', + bidFloor: '0.001' + } + }] + },{ + code: 'native-ad-div', + sizes: [[1, 1]], + nativeParams: { + title: { required: true, len: 75 }, + image: { required: true }, + body: { len: 200 }, + sponsoredBy: { len: 20 }, + icon: { required: false } + }, + bids: [{ + bidder: 'platformio', + params: { + pubId: '29521', // required + siteId: '26047', // required + placementId: '123', + bidFloor: '0.001' } }] }]; diff --git a/modules/pre1api.js b/modules/pre1api.js new file mode 100644 index 00000000000..707d10fbfd8 --- /dev/null +++ b/modules/pre1api.js @@ -0,0 +1,156 @@ + +/** + pre1api module + + This module supports backwards compatibility for those who need extra time to re-code their pages to work with the + Prebid 1.0 API. Use of this backwards compatibility module is recommended only as an interim solution. + + It provides equivalents for the following variables and functions that were deprecated in PBJS 1.0: + - pbjs._winningBids + - pbjs._bidsReceived + - pbjs._bidsRequested + - pbjs._adUnitCodes + - pbjs._adsReceived + - pbjs.cbTimeout + - pbjs.addCallback() + - pbjs.removeCallback() + - pbjs.allBidsAvailable() + - pbjs.bidderTimeout + - pbjs.logging + - pbjs.publisherDomain + - pbjs.setPriceGranularity() + - pbjs.enableSendAllBids() // and also defaults this value to `false` like pre-1.0 + - pbjs.setBidderSequence() + - pbjs.setS2SConfig() // and makes endpoints optional again (defaulting to the appnexus endpoints) + + This will not support the pre-1.0 sizeMapping feature. + + The drawback is that this module disables concurrency for requestBids(), queueing them as was done in pre-1.0. Anytime + an auction request is queued or one of these APIs is accessed it will display a deprecation warning in the console if + logging is enabled. So while this is useful for those that need more time to migrate, it eliminates one of the best + features of PBJS 1.0 as is required to emulate the old API. + */ + +import {config} from 'src/config'; +import {logWarn, logInfo} from 'src/utils'; + +const MODULE_NAME = 'pre-1.0 API'; + +let pbjs = window['$$PREBID_GLOBAL$$']; + +logInfo(`loading ${MODULE_NAME} module and patching prebid with deprecated APIs.`); + +let auctionQueue = []; + +let emptyFn = () => []; + +Object.defineProperty(pbjs, '_winningBids', { + get: () => pbjs.getAllWinningBids() +}); + +let auctionPropMap = { + _bidsReceived: auction => auction.getBidsReceived(), + _bidsRequested: auction => auction.getBidRequests(), + _adUnitCodes: auction => auction.getAdUnitCodes(), + allBidsAvailable: auction => auction.getBidRequests().every((bidRequest) => bidRequest.doneCbCallCount >= 1) +}; + +let configPropMap = { + cbTimeout: 'bidderTimeout', + bidderTimeout: 'bidderTimeout', + logging: 'debug', + publisherDomain: 'publisherDomain', + enableSendAllBids: 'enableSendAllBids', + setPriceGranularity: 'priceGranularity', + setBidderSequence: 'bidderSequence', + setS2SConfig: 's2sConfig' +}; + +pbjs.addCallback = pbjs.onEvent; +pbjs.removeCallback = pbjs.offEvent; + +// can't see anywhere that this was used, but it is listed in Prebid 1.0 transition guide... +// so just adding as empty array +pbjs._adsReceived = []; + +config.setDefaults({ + enableSendAllBids: false, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } +}); + +let currAuction = { + getBidsReceived: emptyFn, + getBidsRequested: emptyFn, + getAdUnitCodes: emptyFn, + getTimeout: () => config.getConfig('bidderTimeout') +}; + +// we need to intercept s2sConfig rather than call setConfig or setDefaults directly, otherwise the code will fail when +// the server adapter attempts to validate the configuration passed in by the publisher +config.setConfig.addHook((config, next) => { + if (config.s2sConfig) { + config.s2sConfig = Object.assign({ + endpoint: 'https://prebid.adnxs.com/pbs/v1/auction', + syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' + }, config.s2sConfig); + } + next(config); +}); + +/** + * Hook to queue and disallow concurrent auctions (as Prebid would function pre 1.0) + */ +pbjs.requestBids.addHook((config, next = config) => { + auctionQueue.push(() => { + let oldHandler = config.bidsBackHandler; + config.bidsBackHandler = (...args) => { + if (typeof oldHandler === 'function') { + oldHandler.apply(null, args); + } + + auctionQueue.shift(); + if (auctionQueue[0]) { + auctionQueue[0](); + } + }; + + currAuction = next(config); + }); + + if (auctionQueue.length === 1) { + auctionQueue[0](); + } else { + logWarn(`${MODULE_NAME} module: concurrency has been disabled and "$$PREBID_GLOBAL$$.requestBids" call was queued`); + } +}, 100); + +Object.keys(auctionPropMap).forEach(prop => { + if (prop === 'allBidsAvailable') { + pbjs[prop] = deprecated(prop, () => auctionPropMap[prop](currAuction)); + } + Object.defineProperty(pbjs, prop, { + get: deprecated(prop, () => auctionPropMap[prop](currAuction)) + }); +}); + +Object.keys(configPropMap).forEach(prop => { + if (prop === 'enableSendAllBids') { + pbjs[prop] = deprecated(prop, () => config.setConfig({[prop]: true})); + } else if (prop.lastIndexOf('set', 0) === 0) { + pbjs[prop] = deprecated(prop, value => config.setConfig({[configPropMap[prop]]: value})); + } else { + Object.defineProperty(pbjs, prop, { + get: deprecated(prop, () => config.getConfig(configPropMap[prop])), + set: deprecated(prop, value => config.setConfig({[configPropMap[prop]]: value})) + }); + } +}); + +function deprecated(name, fn) { + return (...args) => { + logWarn(`${MODULE_NAME} module: accessed deprecated API "$$PREBID_GLOBAL$$.${name}"`); + return fn.apply(null, args); + }; +} diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index 0540d325c79..e222ecb446b 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -166,6 +166,23 @@ const paramTypes = { }, }; +function _getDigiTrustQueryParams() { + function getDigiTrustId() { + let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; + } + return { + id: digiTrustId.id, + keyv: digiTrustId.keyv, + pref: 0 + }; +} + /** * Bidder adapter for Prebid Server */ @@ -193,6 +210,8 @@ export function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { const isDebug = !!getConfig('debug'); + const app = !!getConfig('app'); + const device = !!getConfig('device'); const adUnits = utils.deepClone(s2sBidRequest.ad_units); adUnits.forEach(adUnit => { let videoMediaType = utils.deepAccess(adUnit, 'mediaTypes.video'); @@ -215,9 +234,17 @@ export function PrebidServer() { url: utils.getTopWindowUrl(), prebid_version: '$prebid.version$', ad_units: adUnits.filter(hasSizes), - is_debug: isDebug + is_debug: isDebug, + device: device, + app: app }; + let digiTrust = _getDigiTrustQueryParams(); + + if (digiTrust) { + requestJson.digiTrust = digiTrust; + } + // in case config.bidders contains invalid bidders, we only process those we sent requests for. const requestedBidders = requestJson.ad_units.map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)).reduce(utils.flatten).filter(utils.uniques); function processResponse(response) { @@ -254,7 +281,7 @@ export function PrebidServer() { requestedBidders.forEach(bidder => { let clientAdapter = adaptermanager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { - clientAdapter.registerSyncs(); + clientAdapter.registerSyncs([]); } }); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js new file mode 100644 index 00000000000..dfcde047580 --- /dev/null +++ b/modules/pubmaticBidAdapter.js @@ -0,0 +1,291 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const constants = require('src/constants.json'); + +const BIDDER_CODE = 'pubmatic'; +const ENDPOINT = '//hbopenbid.pubmatic.com/translator?source=prebid-client'; +const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; +const CURRENCY = 'USD'; +const AUCTION_TYPE = 1; +const UNDEFINED = undefined; +const CUSTOM_PARAMS = { + 'kadpageurl': '', // Custom page url + 'gender': '', // User gender + 'yob': '', // User year of birth + 'lat': '', // User location - Latitude + 'lon': '', // User Location - Longitude + 'wiid': '', // OpenWrap Wrapper Impression ID + 'profId': '', // OpenWrap Legacy: Profile ID + 'verId': '' // OpenWrap Legacy: version ID +}; +const NET_REVENUE = false; + +let publisherId = 0; + +function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + +function _parseSlotParam(paramName, paramValue) { + if (!utils.isStr(paramValue)) { + paramValue && utils.logWarn('PubMatic: Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + return UNDEFINED; + } + + switch (paramName) { + case 'pmzoneid': + return paramValue.split(',').slice(0, 50).map(id => id.trim()).join(); + case 'kadfloor': + return parseFloat(paramValue) || UNDEFINED; + case 'lat': + return parseFloat(paramValue) || UNDEFINED; + case 'lon': + return parseFloat(paramValue) || UNDEFINED; + case 'yob': + return parseInt(paramValue) || UNDEFINED; + default: + return paramValue; + } +} + +function _cleanSlot(slotName) { + if (utils.isStr(slotName)) { + return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); + } + return ''; +} + +function _parseAdSlot(bid) { + bid.params.adUnit = ''; + bid.params.adUnitIndex = '0'; + bid.params.width = 0; + bid.params.height = 0; + + bid.params.adSlot = _cleanSlot(bid.params.adSlot); + + var slot = bid.params.adSlot; + var splits = slot.split(':'); + + slot = splits[0]; + if (splits.length == 2) { + bid.params.adUnitIndex = splits[1]; + } + splits = slot.split('@'); + if (splits.length != 2) { + utils.logWarn('AdSlot Error: adSlot not in required format'); + return; + } + bid.params.adUnit = splits[0]; + splits = splits[1].split('x'); + if (splits.length != 2) { + utils.logWarn('AdSlot Error: adSlot not in required format'); + return; + } + bid.params.width = parseInt(splits[0]); + bid.params.height = parseInt(splits[1]); +} + +function _initConf() { + var conf = {}; + conf.pageURL = utils.getTopWindowUrl(); + conf.refURL = utils.getTopWindowReferrer(); + return conf; +} + +function _handleCustomParams(params, conf) { + if (!conf.kadpageurl) { + conf.kadpageurl = conf.pageURL; + } + + var key, value, entry; + for (key in CUSTOM_PARAMS) { + if (CUSTOM_PARAMS.hasOwnProperty(key)) { + value = params[key]; + if (value) { + entry = CUSTOM_PARAMS[key]; + + if (typeof entry === 'object') { + // will be used in future when we want to process a custom param before using + // 'keyname': {f: function() {}} + value = entry.f(value, conf); + } + + if (utils.isStr(value)) { + conf[key] = value; + } else { + utils.logWarn('PubMatic: Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + } + } + } + } + return conf; +} + +function _createOrtbTemplate(conf) { + return { + id: '' + new Date().getTime(), + at: AUCTION_TYPE, + cur: [CURRENCY], + imp: [], + site: { + page: conf.pageURL, + ref: conf.refURL, + publisher: {} + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + user: {}, + ext: {} + }; +} + +function _createImpressionObject(bid, conf) { + return { + id: bid.bidId, + tagid: bid.params.adUnit, + bidfloor: _parseSlotParam('kadfloor', bid.params.kadfloor), + secure: window.location.protocol === 'https:' ? 1 : 0, + banner: { + pos: 0, + w: bid.params.width, + h: bid.params.height, + topframe: utils.inIframe() ? 0 : 1, + }, + ext: { + pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) + } + }; +} + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: bid => { + if (bid && bid.params) { + if (!utils.isStr(bid.params.publisherId)) { + utils.logWarn('PubMatic Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + return false; + } + if (!utils.isStr(bid.params.adSlot)) { + utils.logWarn('PubMatic: adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + return false; + } + return true; + } + return false; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: validBidRequests => { + var conf = _initConf(); + var payload = _createOrtbTemplate(conf); + validBidRequests.forEach(bid => { + _parseAdSlot(bid); + if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { + utils.logWarn('PubMatic: Skipping the non-standard adslot:', bid.params.adSlot, bid); + return; + } + conf.pubId = conf.pubId || bid.params.publisherId; + conf = _handleCustomParams(bid.params, conf); + conf.transactionId = bid.transactionId; + payload.imp.push(_createImpressionObject(bid, conf)); + }); + + if (payload.imp.length == 0) { + return; + } + + payload.site.publisher.id = conf.pubId.trim(); + publisherId = conf.pubId.trim(); + payload.ext.wrapper = {}; + payload.ext.wrapper.profile = conf.profId || UNDEFINED; + payload.ext.wrapper.version = conf.verId || UNDEFINED; + payload.ext.wrapper.wiid = conf.wiid || UNDEFINED; + payload.ext.wrapper.wv = constants.REPO_AND_VERSION; + payload.ext.wrapper.transactionId = conf.transactionId; + payload.ext.wrapper.wp = 'pbjs'; + payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); + payload.user.geo = {}; + payload.user.geo.lat = _parseSlotParam('lat', conf.lat); + payload.user.geo.lon = _parseSlotParam('lon', conf.lon); + payload.user.yob = _parseSlotParam('yob', conf.yob); + payload.device.geo = {}; + payload.device.geo.lat = _parseSlotParam('lat', conf.lat); + payload.device.geo.lon = _parseSlotParam('lon', conf.lon); + payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); + payload.site.domain = _getDomainFromURL(payload.site.page); + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload) + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (response, request) => { + const bidResponses = []; + try { + if (response.body && response.body.seatbid && response.body.seatbid[0] && response.body.seatbid[0].bid) { + response.body.seatbid[0].bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: 300, + referrer: utils.getTopWindowUrl(), + ad: bid.adm + }; + bidResponses.push(newBid); + }); + } + } catch (error) { + utils.logError(error); + } + return bidResponses; + }, + + /** + * Register User Sync. + */ + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USYNCURL + publisherId + }]; + } else { + utils.logWarn('PubMatic: Please enable iframe based user sync.'); + } + } +}; + +registerBidder(spec); diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md new file mode 100644 index 00000000000..768b3c541f6 --- /dev/null +++ b/modules/pubmaticBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: PubMatic Bid Adapter +Module Type: Bidder Adapter +Maintainer: header-bidding@pubmatic.com +``` + +# Description + +Connects to PubMatic exchange for bids. + +PubMatic bid adapter supports Banner currently. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: 'test-div', + sizes: [ + [300, 250], + [728, 90] + ], + bids: [{ + bidder: 'pubmatic', + params: { + publisherId: '156209', // required + adSlot: 'pubmatic_test2@300x250', // required + pmzoneid: 'zone1, zone11', // optional + lat: '40.712775', // optional + lon: '-74.005973', // optional + yob: '1982', // optional + kadpageurl: 'www.test.com', // optional + gender: 'M', // optional + kadfloor: '0.50' // optional + } + }] +} +``` + +### Configuration + +PubMatic recommends the UserSync configuration below. Without it, the PubMatic adapter will not able to perform user syncs, which lowers match rate and reduces monetization. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + enabledBidders: ['pubmatic'], + syncDelay: 6000 + }}); +``` +Note: Combine the above the configuration with any other UserSync configuration. Multiple setConfig() calls overwrite each other and only last call for a given attribute will take effect. diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 2c1f6f9174d..fc637cc9fff 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { aliases: ['pulseLite', 'pulsepointLite'], - supportedMediaTypes: ['native'], + supportedMediaTypes: ['banner', 'native'], isBidRequestValid: bid => ( !!(bid && bid.params && bid.params.cp && bid.params.ct) diff --git a/modules/rockyouBidAdapter.js b/modules/rockyouBidAdapter.js new file mode 100644 index 00000000000..975756d18bc --- /dev/null +++ b/modules/rockyouBidAdapter.js @@ -0,0 +1,363 @@ +import * as utils from 'src/utils'; +import { Renderer } from 'src/Renderer'; +import { BANNER, VIDEO } from 'src/mediaTypes'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'rockyou'; + +const BASE_REQUEST_PATH = 'https://tas.rockyou.net/servlet/rotator/'; +const IFRAME_SYNC_URL = 'https://prebid.tex-sync.rockyou.net/usersync2/tas'; +const VAST_PLAYER_LOCATION = 'https://rya-static.rockyou.com/rya/js/PreBidPlayer.js'; +export const ROTATION_ZONE = 'openrtbprod'; + +let isBidRequestValid = (bid) => { + return !!bid.params && !!bid.params.placementId; +}; + +/** +* The RockYou Ad Serving system currently only accepts one placementId +* per Ad request. For this reason, the first placementId indicated +* will be chosen as the predominant placementId for this request. +*/ +let determineOptimalPlacementId = (bidRequest) => { + return bidRequest.params.placementId; +} + +let determineOptimalRequestId = (bidRequest) => { + return bidRequest.bidId; +} + +let buildSiteComponent = (bidRequest) => { + let topLocation = utils.getTopWindowLocation(); + + let site = { + domain: topLocation.hostname, + page: topLocation.href, + ref: topLocation.origin + }; + + return site; +} + +let buildDeviceComponent = (bidRequest) => { + let device = { + js: 1, + language: ('language' in navigator) ? navigator.language : null + }; + + return device; +}; + +let extractValidSize = (bidRequest) => { + let width = null; + let height = null; + + if (!utils.isEmpty(bidRequest.sizes)) { + // Ensure the size array is normalized + let conformingSize = utils.parseSizesInput(bidRequest.sizes); + + if (!utils.isEmpty(conformingSize) && conformingSize[0] != null) { + // Currently only the first size is utilized + let splitSizes = conformingSize[0].split('x'); + + width = parseInt(splitSizes[0]); + height = parseInt(splitSizes[1]); + } + } + + return { + w: width, + h: height + }; +}; + +let generateVideoComponent = (bidRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateBannerComponent = (bidRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateImpBody = (bidRequest) => { + let mediaTypes = bidRequest.mediaTypes; + + let banner = null; + let video = null; + + // Assume banner if the mediatype is not included + if (mediaTypes && mediaTypes.video) { + video = generateVideoComponent(bidRequest); + } else { + banner = generateBannerComponent(bidRequest); + } + + return { + id: bidRequest.index, + banner: banner, + video: video + }; +} + +let generatePayload = (bidRequests) => { + // Generate the expected OpenRTB payload + + let rootBidRequest = bidRequests[0]; + + let index = 1; + bidRequests.forEach((bidRequest) => { + bidRequest.index = index; + index += 1; + }) + + let payload = { + id: determineOptimalRequestId(rootBidRequest), + site: buildSiteComponent(rootBidRequest), + device: buildDeviceComponent(rootBidRequest), + imp: bidRequests.map(generateImpBody) + }; + + return JSON.stringify(payload); +}; + +let overridableProperties = (request) => { + let rendererLocation = VAST_PLAYER_LOCATION; + let baseRequestPath = BASE_REQUEST_PATH; + let rotationZone = ROTATION_ZONE; + + if (!utils.isEmpty(request.rendererOverride)) { + rendererLocation = request.rendererOverride; + } + + if (request.params) { + if (!utils.isEmpty(request.params.baseRequestPath)) { + baseRequestPath = request.params.baseRequestPath; + } + + if (!utils.isEmpty(request.params.rotationZone)) { + rotationZone = request.params.rotationZone; + } + } + + return { + rendererLocation, + baseRequestPath, + rotationZone + } +} + +let buildRequests = (validBidRequests, requestRoot) => { + const requestType = 'POST'; + + let requestUrl = null; + let requestPayload = null; + let mediaTypes = null; + let adUnitCode = null; + let rendererOverride = null; + + // Due to the nature of how URLs are generated, there must + // be at least one bid request present for this to function + // correctly + if (!utils.isEmpty(validBidRequests)) { + let headBidRequest = validBidRequests[0]; + + let serverLocations = overridableProperties(headBidRequest); + + // requestUrl is the full endpoint w/ relevant adspot paramters + let placementId = determineOptimalPlacementId(headBidRequest); + requestUrl = `${serverLocations.baseRequestPath}${placementId}/0/vo?z=${serverLocations.rotationZone}`; + + // requestPayload is the POST body JSON for the OpenRtb request + requestPayload = generatePayload(validBidRequests); + + mediaTypes = headBidRequest.mediaTypes; + adUnitCode = headBidRequest.adUnitCode; + rendererOverride = headBidRequest.rendererOverride; + } + const result = { + method: requestType, + type: requestType, + url: requestUrl, + data: requestPayload, + mediaTypes, + requestId: requestRoot.bidderRequestId, + adUnitCode, + rendererOverride + }; + + return result; +}; + +let outstreamRender = (bid) => { + // push to render queue because player may not be loaded yet + bid.renderer.push(() => { + let adUnitCode = bid.renderer.config.adUnitCode; + + try { + RockYouVastPlayer.render(adUnitCode, bid, playerCallbacks(bid.renderer)); + } catch (pbe) { + utils.logError(pbe); + } + }); +} + +let rockYouEventTranslation = (rockYouEvent) => { + let translated; + switch (rockYouEvent) { + case 'LOAD': + translated = 'loaded'; + break; + case 'IMPRESSION': + translated = 'impression'; + break; + case 'COMPLETE': + case 'ERROR': + translated = 'ended' + break; + } + + return translated; +} + +let playerCallbacks = (renderer) => (id, eventName) => { + eventName = rockYouEventTranslation(eventName); + + if (eventName) { + renderer.handleVideoEvent({ id, eventName }); + } +}; + +let generateRenderer = (bid, adUnitCode, rendererLocation) => { + let renderer = Renderer.install({ + url: rendererLocation, + config: { + adText: `RockYou Outstream Video Ad`, + adUnitCode: adUnitCode + }, + id: bid.id + }); + + bid.renderer = renderer; + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('RockYou outstream video impression event'), + loaded: () => utils.logMessage('RockYou outstream video loaded event'), + ended: () => { + utils.logMessage('RockYou outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + + return renderer; +}; + +let interpretResponse = (serverResponse, request) => { + let responses = []; + + if (serverResponse.body) { + let responseBody = serverResponse.body; + + if (responseBody != null) { + let seatBids = responseBody.seatbid; + + if (!(utils.isEmpty(seatBids) || + utils.isEmpty(seatBids[0].bid))) { + let bid = seatBids[0].bid[0]; + + // handle any values that may end up undefined + let nullify = (value) => typeof value === 'undefined' ? null : value; + + let ttl = null; + if (bid.ext) { + ttl = nullify(bid.ext.ttl); + } + + let bidWidth = nullify(bid.w); + let bidHeight = nullify(bid.h); + + let bannerCreative = null; + let videoXml = null; + let mediaType = null; + let renderer = null; + + if (request.mediaTypes && request.mediaTypes.video) { + videoXml = bid.adm; + mediaType = VIDEO; + + let serversideLocations = overridableProperties(request); + + renderer = generateRenderer(bid, request.adUnitCode, serversideLocations.rendererLocation); + } else { + bannerCreative = bid.adm; + } + + let response = { + requestId: responseBody.id, + cpm: bid.price, + width: bidWidth, + height: bidHeight, + ad: bannerCreative, + ttl: ttl, + creativeId: bid.adid, + netRevenue: true, + currency: responseBody.cur, + vastUrl: null, + vastXml: videoXml, + dealId: null, + mediaType: mediaType, + renderer: renderer + }; + + responses.push(response); + } + } + } + + return responses; +}; + +let getUserSyncs = (syncOptions, serverResponses) => { + const syncs = [] + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + }); + } + + return syncs; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['ry'], + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, + getUserSyncs: getUserSyncs, + supportedMediaTypes: [BANNER, VIDEO] +}; + +export const internals = { + playerCallbacks, + generateRenderer +} + +registerBidder(spec); diff --git a/modules/rockyouBidAdapter.md b/modules/rockyouBidAdapter.md new file mode 100644 index 00000000000..8cc9ba371d7 --- /dev/null +++ b/modules/rockyouBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +``` +Module Name: RockYou Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid.adapter@rockyou.com +``` + +# Description + +Connects to the RockYou exchange for bids. + +The RockYou bid adapter supports Banner and Video. + +For publishers who wish to be set up on the RockYou Ad Network, please contact +publishers@rockyou.com. + +RockYou user syncing requires the `userSync.iframeEnabled` property be set to `true`. + +# Test PARAMETERS +``` +var adUnits = [ + + // Banner adUnit + { + code: 'banner-div', + sizes: [[720, 480]], + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'rockyou', + params: { + placementId: '4322' + } + }] + }, + + // Video (outstream) + { + code: 'video-outstream', + sizes: [[720, 480]], + + mediaType: 'video', + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'rockyou', + params: { + placementId: '4307' + } + }] + } +] +``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 76a9095be72..ee490e55c15 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -73,7 +73,7 @@ utils._each(sizeMap, (item, key) => sizeMap[item] = key); export const spec = { code: 'rubicon', aliases: ['rubiconLite'], - supportedMediaTypes: ['video'], + supportedMediaTypes: ['banner', 'video'], /** * @param {object} bid * @return boolean @@ -112,9 +112,10 @@ export const spec = { if (bidRequest.mediaType === 'video') { let params = bidRequest.params; let size = parseSizes(bidRequest); + let page_rf = !params.referrer ? utils.getTopWindowUrl() : params.referrer; let data = { - page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, + page_url: params.secure ? page_rf.replace(/^http:/i, 'https:') : page_rf, resolution: _getScreenResolution(), account_id: params.accountId, integration: INTEGRATION, @@ -265,6 +266,7 @@ export const spec = { requestId: bidRequest.bidId, currency: 'USD', creativeId: ad.creative_id, + mediaType: ad.creative_type, cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes @@ -275,6 +277,7 @@ export const spec = { bid.height = bidRequest.params.video.playerHeight; bid.vastUrl = ad.creative_depot_url; bid.impression_id = ad.impression_id; + bid.videoCacheKey = ad.impression_id; } else { bid.ad = _renderCreative(ad.script, ad.impression_id); [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js index 6e4ae34bd76..09b94be967b 100644 --- a/modules/sekindoUMBidAdapter.js +++ b/modules/sekindoUMBidAdapter.js @@ -2,7 +2,7 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; export const spec = { code: 'sekindoUM', - supportedMediaTypes: ['video'], + supportedMediaTypes: ['banner', 'video'], /** * Determines whether or not the given bid request is valid. * diff --git a/modules/serverbidBidAdapter.js b/modules/serverbidBidAdapter.js index d120e5c4306..24bb5aa6255 100644 --- a/modules/serverbidBidAdapter.js +++ b/modules/serverbidBidAdapter.js @@ -18,12 +18,18 @@ const CONFIG = { }, 'adsparc': { 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'automatad': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' } }; +let siteId = 0; +let bidder = 'serverbid'; + export const spec = { code: BIDDER_CODE, - aliases: ['connectad', 'onefiftytwo', 'insticator', 'adsparc'], + aliases: ['connectad', 'onefiftytwo', 'insticator', 'adsparc', 'automatad'], /** * Determines whether or not the given bid request is valid. @@ -59,6 +65,10 @@ export const spec = { let ENDPOINT_URL; + // These variables are used in creating the user sync URL. + siteId = validBidRequests[0].params.siteId; + bidder = validBidRequests[0].params.bidder; + const data = Object.assign({ placements: [], time: Date.now(), @@ -137,7 +147,21 @@ export const spec = { }, getUserSyncs: function(syncOptions) { - return []; + if (syncOptions.iframeEnabled) { + if (bidder === 'connectad') { + return [{ + type: 'iframe', + url: '//cdn.connectad.io/connectmyusers.php' + }]; + } else { + return [{ + type: 'iframe', + url: '//s.zkcdn.net/ss/' + siteId + '.html' + }]; + } + } else { + utils.logWarn(bidder + ': Please enable iframe based user syncing.'); + } } }; @@ -181,6 +205,10 @@ sizeMap[3230] = '970x280'; sizeMap[429] = '486x60'; sizeMap[374] = '700x500'; sizeMap[934] = '300x1050'; +sizeMap[1578] = '320x100'; +sizeMap[331] = '320x250'; +sizeMap[3301] = '320x267'; +sizeMap[2730] = '728x250'; function getSize(sizes) { const result = []; diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md index 934362c69c4..87b51e665e2 100644 --- a/modules/serverbidBidAdapter.md +++ b/modules/serverbidBidAdapter.md @@ -35,7 +35,7 @@ Connects to Serverbid for receiving bids from configured demand sources. params: { networkId: '9969', siteId: '980639', - zoneId: '178503' + zoneIds: [178503] } } ] diff --git a/modules/serverbidServerBidAdapter.js b/modules/serverbidServerBidAdapter.js index 4be96a09bd6..1025d29a6c0 100644 --- a/modules/serverbidServerBidAdapter.js +++ b/modules/serverbidServerBidAdapter.js @@ -7,7 +7,7 @@ import { config } from 'src/config'; const TYPE = S2S.SRC; const getConfig = config.getConfig; -const REQUIRED_S2S_CONFIG_KEYS = ['siteId', 'networkId', 'bidders', 'endpoint']; +const REQUIRED_S2S_CONFIG_KEYS = ['siteId', 'networkId', 'bidders']; let _s2sConfig; @@ -100,30 +100,30 @@ ServerBidServerAdapter = function ServerBidServerAdapter() { function _callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { let bidRequest = s2sBidRequest; - // one request per ad unit + const data = { + placements: [], + time: Date.now(), + user: {}, + url: utils.getTopWindowUrl(), + referrer: document.referrer, + enableBotFiltering: true, + includePricingData: true, + parallel: true + }; + const allBids = []; + for (let i = 0; i < bidRequest.ad_units.length; i++) { let adunit = bidRequest.ad_units[i]; let siteId = _s2sConfig.siteId; let networkId = getLocalConfig().networkId; let sizes = adunit.sizes; - const data = { - placements: [], - time: Date.now(), - user: {}, - url: utils.getTopWindowUrl(), - referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true - }; - - const bids = adunit.bids || []; - + var bids = adunit.bids || []; // one placement for each of the bids for (let i = 0; i < bids.length; i++) { const bid = bids[i]; bid.code = adunit.code; + allBids.push(bid); const placement = Object.assign({}, { divName: bid.bid_id, @@ -138,9 +138,9 @@ ServerBidServerAdapter = function ServerBidServerAdapter() { data.placements.push(placement); } } - if (data.placements.length) { - ajax(BASE_URI, _responseCallback(addBidResponse, bids, done), JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); - } + } + if (data.placements.length) { + ajax(BASE_URI, _responseCallback(addBidResponse, allBids, done), JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); } } diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js new file mode 100644 index 00000000000..4828f3a36a8 --- /dev/null +++ b/modules/smartadserverBidAdapter.js @@ -0,0 +1,92 @@ +import * as utils from 'src/utils'; +import { + config +} from 'src/config'; +import { + registerBidder +} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'smartadserver'; +export const spec = { + code: BIDDER_CODE, + aliases: ['smart'], // short 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 && bid.params.siteId && bid.params.pageId && bid.params.formatId && bid.params.domain); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + // use bidderRequest.bids[] to get bidder-dependent request info + + // if your bidder supports multiple currencies, use config.getConfig(currency) + // to find which one the ad server needs + + // pull requested transaction ID from bidderRequest.bids[].transactionId + var bid = validBidRequests[0]; + const payload = { + siteid: bid.params.siteId, + pageid: bid.params.pageId, + formatid: bid.params.formatId, + currencyCode: config.getConfig('currency.adServerCurrency'), + bidfloor: bid.params.bidfloor || 0.0, + targeting: bid.params.target && bid.params.target != '' ? bid.params.target : undefined, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + pageDomain: utils.getTopWindowUrl(), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId + }; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: bid.params.domain + '/prebid/v1', + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + var response = serverResponse.body; + try { + if (response) { + const bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + dealId: response.dealId, + currency: response.currency, + netRevenue: response.isNetCpm, + ttl: response.ttl, + referrer: utils.getTopWindowUrl(), + adUrl: response.adUrl, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + } catch (error) { + console.log('Error while parsing smart server response'); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md new file mode 100644 index 00000000000..f904aa40b3a --- /dev/null +++ b/modules/smartadserverBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Smart Ad Server Bidder Adapter +Module Type: Bidder Adapter +Maintainer: gcarnec@smartadserver.com +``` + +# Description + +Connect to Smart for bids. + +The Smart adapter requires setup and approval from the Smart team. +Please reach out to your Technical account manager for more information. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smart", + params: { + domain: 'http://prg.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 62913 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js new file mode 100644 index 00000000000..e453d84d586 --- /dev/null +++ b/modules/sonobiBidAdapter.js @@ -0,0 +1,143 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import { getTopWindowLocation, parseSizesInput } from 'src/utils'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'sonobi'; +const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; +const PAGEVIEW_ID = utils.generateUUID(); + +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: bid => !!(bid.params && (bid.params.ad_unit || bid.params.placement_id) && (bid.params.sizes || bid.sizes)), + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return {object} ServerRequest - Info describing the request to the server. + */ + buildRequests: (validBidRequests) => { + const bids = validBidRequests.map(bid => { + let slotIdentifier = _validateSlot(bid) + if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { + slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier + return { + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}` + } + } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { + return { + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}` + } + } else { + utils.logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); + } + }); + + const payload = { + 'key_maker': JSON.stringify(Object.assign({}, ...bids)), + 'ref': getTopWindowLocation().host, + 's': utils.generateUUID(), + 'pv': PAGEVIEW_ID, + }; + + if (validBidRequests[0].params.hfa) { + payload.hfa = validBidRequests[0].params.hfa; + } + + return { + method: 'GET', + url: STR_ENDPOINT, + withCredentials: true, + data: payload + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + const bidResponse = serverResponse.body; + const bidsReturned = []; + + if (Object.keys(bidResponse.slots).length === 0) { + return bidsReturned; + } + + Object.keys(bidResponse.slots).forEach(slot => { + const bid = bidResponse.slots[slot]; + + if (bid.sbi_aid && bid.sbi_mouse && bid.sbi_size) { + const bids = { + requestId: slot.split('|').slice(-1)[0], + cpm: Number(bid.sbi_mouse), + width: Number(bid.sbi_size.split('x')[0]) || 1, + height: Number(bid.sbi_size.split('x')[1]) || 1, + ad: _creative(bidResponse.sbi_dc, bid.sbi_aid), + ttl: 500, + creativeId: bid.sbi_aid, + netRevenue: true, + currency: 'USD', + }; + + if (bid.sbi_dozer) { + bids.dealId = bid.sbi_dozer; + } + + bidsReturned.push(bids); + } + }); + return bidsReturned; + }, + /** + * Register User Sync. + */ + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.pixelEnabled && serverResponses[0].body.sbi_px) { + serverResponses[0].body.sbi_px.forEach(pixel => { + syncs.push({ + type: pixel.type, + url: pixel.url + }); + }); + } + return syncs; + } +} + +function _validateSize (bid) { + if (bid.params.sizes) { + return parseSizesInput(bid.params.sizes).join(','); + } + return parseSizesInput(bid.sizes).join(','); +} + +function _validateSlot (bid) { + if (bid.params.ad_unit) { + return bid.params.ad_unit; + } + return bid.params.placement_id; +} + +function _validateFloor (bid) { + if (bid.params.floor) { + return `|f=${bid.params.floor}`; + } + return ''; +} + +function _creative (sbi_dc, sbi_aid) { + const src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null'; + return ''; +} + +registerBidder(spec); diff --git a/modules/sonobiBidAdapter.md b/modules/sonobiBidAdapter.md new file mode 100644 index 00000000000..91e4a0e2b2e --- /dev/null +++ b/modules/sonobiBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Sonobi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: apex.prebid@sonobi.com +``` + +# Description + +Module that connects to Sonobi's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'adUnit_af', + sizes: [[300, 250], [300, 600]], // a display size + bids: [ + { + bidder: 'sonobi', + params: { + ad_unit: '/7780971/sparks_prebid_MR', + placement_id: '1a2b3c4d5e6f1a2b3c4d', // ad_unit and placement_id are mutually exclusive + sizes: [[300, 250], [300, 600]], + floor: 1 // optional + } + } + ] + } + ]; +``` diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js new file mode 100644 index 00000000000..7f652e38c3d --- /dev/null +++ b/modules/ucfunnelBidAdapter.js @@ -0,0 +1,95 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const VER = 'ADGENT_PREBID-2018011501'; +const BID_REQUEST_BASE_URL = '//hb.aralego.com/header'; +const UCFUNNEL_BIDDER_CODE = 'ucfunnel'; + +export const spec = { + code: UCFUNNEL_BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Check if the bid is a valid zone ID in either number or string form + * @param {object} bid the ucfunnel bid to validate + * @return boolean for whether or not a bid is valid + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.adid && typeof bid.params.adid === 'string'); + }, + + /** + * Format the bid request object for our endpoint + * @param {BidRequest[]} bidRequests Array of ucfunnel bidders + * @return object of parameters for Prebid AJAX request + */ + buildRequests: function(validBidRequests) { + var bidRequests = []; + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; + + var ucfunnelUrlParams = buildUrlParams(bid); + + bidRequests.push({ + method: 'GET', + url: BID_REQUEST_BASE_URL, + bidRequest: bid, + data: ucfunnelUrlParams + }); + } + return bidRequests; + }, + + /** + * Format ucfunnel responses as Prebid bid responses + * @param {ucfunnelResponseObj} ucfunnelResponse A successful response from ucfunnel. + * @return {Bid[]} An array of formatted bids. + */ + interpretResponse: function (ucfunnelResponseObj, request) { + var bidResponses = []; + var bidRequest = request.bidRequest; + var responseBody = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; + + bidResponses.push({ + requestId: bidRequest.bidId, + cpm: responseBody.cpm || 0, + width: responseBody.width, + height: responseBody.height, + creativeId: responseBody.ad_id, + dealId: responseBody.deal || null, + currency: 'USD', + netRevenue: true, + ttl: 1000, + mediaType: BANNER, + ad: responseBody.adm + }); + + return bidResponses; + } +}; +registerBidder(spec); + +function buildUrlParams(bid) { + const host = utils.getTopWindowLocation().host; + const page = utils.getTopWindowLocation().pathname; + const refer = document.referrer; + const language = navigator.language; + const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + + let queryString = [ + 'ifr', '0', + 'bl', language, + 'je', '1', + 'dnt', dnt, + 'host', host, + 'u', page, + 'ru', refer, + 'adid', utils.getBidIdParameter('adid', bid.params), + 'ver', VER + ]; + + return queryString.reduce( + (memo, curr, index) => + index % 2 === 0 && queryString[index + 1] !== undefined ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, '' + ).slice(0, -1); +} diff --git a/modules/ucfunnelBidAdapter.md b/modules/ucfunnelBidAdapter.md new file mode 100644 index 00000000000..717d2a0089c --- /dev/null +++ b/modules/ucfunnelBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: ucfunnel Bid Adapter +Module Type: Bidder Adapter +Maintainer: ryan.chou@ucfunnel.com +``` + +# Description + +This module connects to ucfunnel's demand sources. It supports display, and rich media formats. +ucfunnel will provide ``adid`` that are specific to your ad type. +Please reach out to ``pr@ucfunnel.com`` to set up an ucfunnel account and above ids. +Use bidder code ```ucfunnel``` for all ucfunnel traffic. + +# Test Parameters + +``` + var adUnits = [ + { + code: 'test-LERC', + sizes: [[300, 250]], + bids: [{ + bidder: 'ucfunnel', + params: { + adid: "test-ad-83444226E44368D1E32E49EEBE6D29" //String - required + } + } + ]; +``` \ No newline at end of file diff --git a/modules/vertamediaBidAdapter.js b/modules/vertamediaBidAdapter.js index b314f6fd872..238495f3966 100644 --- a/modules/vertamediaBidAdapter.js +++ b/modules/vertamediaBidAdapter.js @@ -1,16 +1,19 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; -import {VIDEO} from 'src/mediaTypes'; +import {VIDEO, BANNER} from 'src/mediaTypes'; import {Renderer} from 'src/Renderer'; +import findIndex from 'core-js/library/fn/array/find-index'; -const URL = '//rtb.vertamedia.com/hb/'; +const URL = '//hb2.vertamedia.com/auction/'; const BIDDER_CODE = 'vertamedia'; +const OUTSTREAM = 'outstream'; +const DISPLAY = 'display'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO], + supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { - return Boolean(bid && bid.params && bid.params.aid); + return bid && bid.params && bid.params.aid; }, /** @@ -25,7 +28,7 @@ export const spec = { bidderRequest, method: 'GET', url: URL - } + }; }); }, @@ -38,9 +41,6 @@ export const spec = { interpretResponse: function (serverResponse, {bidderRequest}) { serverResponse = serverResponse.body; const isInvalidValidResp = !serverResponse || !serverResponse.bids || !serverResponse.bids.length; - const videoMediaType = utils.deepAccess(bidderRequest.bids[0], 'mediaTypes.video'); - const context = utils.deepAccess(bidderRequest.bids[0], 'mediaTypes.video.context'); - const isMediaTypeOutstream = (videoMediaType && context === 'outstream'); let bids = []; @@ -54,76 +54,82 @@ export const spec = { } serverResponse.bids.forEach(serverBid => { - if (serverBid.cpm !== 0) { - const bid = createBid(isMediaTypeOutstream, serverBid); + const requestId = findIndex(bidderRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (serverBid.cpm !== 0 && requestId !== -1) { + const bid = createBid(serverBid, getMediaType(bidderRequest.bids[requestId])); + bids.push(bid); } }); return bids; - }, + } }; /** - * Prepare all parameters for request + * Parse mediaType * @param bid {object} * @returns {object} */ function prepareRTBRequestParams(bid) { - let size = getSize(bid.sizes); + const mediaType = utils.deepAccess(bid, 'mediaTypes.video') ? VIDEO : DISPLAY; return { domain: utils.getTopWindowLocation().hostname, callbackId: bid.bidId, aid: bid.params.aid, - h: size.height, - w: size.width + ad_type: mediaType, + sizes: utils.parseSizesInput(bid.sizes).join() }; } /** - * Prepare size for request - * @param requestSizes {array} - * @returns {object} bid The bid to validate + * Prepare all parameters for request + * @param bidderRequest {object} + * @returns {object} */ -function getSize(requestSizes) { - const size = utils.parseSizesInput(requestSizes)[0]; - const parsed = {}; - - if (typeof size !== 'string') { - return parsed; - } - - let parsedSize = size.toUpperCase().split('X'); +function getMediaType(bidderRequest) { + const videoMediaType = utils.deepAccess(bidderRequest, 'mediaTypes.video'); + const context = utils.deepAccess(bidderRequest, 'mediaTypes.video.context'); - return { - height: parseInt(parsedSize[1], 10) || undefined, - width: parseInt(parsedSize[0], 10) || undefined - }; + return !videoMediaType ? DISPLAY : context === OUTSTREAM ? OUTSTREAM : VIDEO; } /** * Configure new bid by response - * @param isMediaTypeOutstream {boolean} * @param bidResponse {object} + * @param mediaType {Object} * @returns {object} */ -function createBid(isMediaTypeOutstream, bidResponse) { +function createBid(bidResponse, mediaType) { let bid = { requestId: bidResponse.requestId, creativeId: bidResponse.cmpId, - vastUrl: bidResponse.vastUrl, height: bidResponse.height, currency: bidResponse.cur, width: bidResponse.width, cpm: bidResponse.cpm, - mediaType: 'video', netRevenue: true, + mediaType, ttl: 3600 }; - if (isMediaTypeOutstream) { + if (mediaType === DISPLAY) { + return Object.assign(bid, { + ad: bidResponse.ad + }); + } + + Object.assign(bid, { + vastUrl: bidResponse.vastUrl + }); + + if (mediaType === OUTSTREAM) { Object.assign(bid, { + mediaType: 'video', adResponse: bidResponse, renderer: newRenderer(bidResponse.requestId) }); @@ -132,11 +138,16 @@ function createBid(isMediaTypeOutstream, bidResponse) { return bid; } +/** + * Create Vertamedia renderer + * @param requestId + * @returns {*} + */ function newRenderer(requestId) { const renderer = Renderer.install({ id: requestId, url: '//player.vertamedia.com/outstream-unit/2.01/outstream.min.js', - loaded: false, + loaded: false }); renderer.setRender(outstreamRender); @@ -144,6 +155,10 @@ function newRenderer(requestId) { return renderer; } +/** + * Initialise Vertamedia outstream + * @param bid + */ function outstreamRender(bid) { bid.renderer.push(() => { window.VOutstreamAPI.initOutstreams([{ diff --git a/modules/vertamediaBidAdapter.md b/modules/vertamediaBidAdapter.md index 0ce4f2cbd58..2f6faf5c840 100644 --- a/modules/vertamediaBidAdapter.md +++ b/modules/vertamediaBidAdapter.md @@ -9,33 +9,57 @@ Get access to multiple demand partners across VertaMedia AdExchange and maximize your yield with VertaMedia header bidding adapter. VertaMedia header bidding adapter connects with VertaMedia demand sources in order to fetch bids. -This adapter provides a solution for accessing Video demand +This adapter provides a solution for accessing Video demand and display demand # Test Parameters ``` - var adUnits = [{ + var adUnits = [ + + // Video instream adUnit + { code: 'div-test-div', - sizes: [[640, 480]], // ad size + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'instream' + } + }, bids: [{ - bidder: 'vertamedia', // adapter name - params: { - aid: 332842 - } + bidder: 'vertamedia', + params: { + aid: 332842 + } }] - }{ + }, + + // Video outstream adUnit + { code: 'outstream-test-div', - sizes: [[640, 480]], // ad size + sizes: [[640, 480]], mediaTypes: { - video: { - context: 'outstream' - } + video: { + context: 'outstream' + } }, bids: [{ - bidder: 'vertamedia', // adapter name - params: { - aid: 332842 - } + bidder: 'vertamedia', + params: { + aid: 332842 + } + }] + }, + + // Banner adUnit + { + code: 'div-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'vertamedia', + params: { + aid: 324758 + } }] - }]; + } + ]; ``` diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js new file mode 100644 index 00000000000..bcfc4e246ac --- /dev/null +++ b/modules/viBidAdapter.js @@ -0,0 +1,69 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const BIDDER_CODE = 'vi'; +const SUPPORTED_MEDIA_TYPES = [BANNER]; + +function isBidRequestValid(bid) { + return !!(bid.params.pubId); +} + +function buildRequests(bidReqs) { + let imps = []; + utils._each(bidReqs, function (bid) { + imps.push({ + id: bid.bidId, + sizes: utils.parseSizesInput(bid.sizes).map(size => size.split('x')), + bidFloor: parseFloat(bid.params.bidFloor) > 0 ? bid.params.bidFloor : 0 + }); + }); + + const bidRequest = { + id: bidReqs[0].requestId, + imps: imps, + publisherId: utils.getBidIdParameter('pubId', bidReqs[0].params), + siteId: utils.getBidIdParameter('siteId', bidReqs[0].params), + cat: utils.getBidIdParameter('cat', bidReqs[0].params), + language: utils.getBidIdParameter('lang', bidReqs[0].params), + domain: utils.getTopWindowLocation().hostname, + page: utils.getTopWindowUrl(), + referrer: utils.getTopWindowReferrer() + }; + return { + method: 'POST', + url: `//pb.vi-serve.com/prebid/bid`, + data: JSON.stringify(bidRequest), + options: {contentType: 'application/json', withCredentials: false} + }; +} + +function interpretResponse(bids) { + let responses = []; + utils._each(bids.body, function(bid) { + responses.push({ + requestId: bid.id, + cpm: parseFloat(bid.price), + width: parseInt(bid.width, 10), + height: parseInt(bid.height, 10), + creativeId: bid.creativeId, + dealId: bid.dealId || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(`${bid.ad}`), + ttl: 60000 + }); + }); + return responses; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/viBidAdapter.md b/modules/viBidAdapter.md new file mode 100644 index 00000000000..23288024fcc --- /dev/null +++ b/modules/viBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +``` +Module Name: vi bid adapter +Module Type: Bidder adapter +Maintainer: support@vi.ai +``` + +# Description + +The video intelligence (vi) adapter integration to the Prebid library. +Connects to vi’s demand sources. +There should be only one ad unit with vi bid adapter on each single page. + +# Test Parameters + +``` +var adUnits = [{ + code: 'div-0', + sizes: [[320, 480]], + bids: [{ + bidder: 'vi', + params: { + pubId: 'sb_test', + lang: 'en-US', + cat: 'IAB1', + bidFloor: 0.05 //optional + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | +| `pubId` | required | Publisher ID, provided by vi | 'sb_test' | +| `lang` | required | Ad language, in ISO 639-1 language code format | 'en-US', 'es-ES', 'de' | +| `cat` | required | Ad IAB category (top-level or subcategory), single one supported | 'IAB1', 'IAB9-1' | +| `bidFloor` | optional | Lowest value of expected bid price | 0.001 | + diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js new file mode 100644 index 00000000000..981f5603a0a --- /dev/null +++ b/modules/vidazooBidAdapter.js @@ -0,0 +1,128 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +export const URL = '//prebid.cliipa.com'; +const BIDDER_CODE = 'vidazoo'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const INTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'img' +}; +const EXTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'image' +}; + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(params.cId && params.pId); +} + +function buildRequest(bid, topWindowUrl, size) { + const {params, bidId} = bid; + const {bidFloor, cId, pId} = params; + // Prebid's util function returns AppNexus style sizes (i.e. 300x250) + const [width, height] = size.split('x'); + + return { + method: 'GET', + url: `${URL}/prebid/${cId}`, + data: { + url: topWindowUrl, + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + publisherId: pId, + width, + height + } + } +} + +function buildRequests(validBidRequests) { + const topWindowUrl = utils.getTopWindowUrl(); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = utils.parseSizesInput(validBidRequest.sizes); + sizes.forEach(size => { + const request = buildRequest(validBidRequest, topWindowUrl, size); + requests.push(request); + }); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {creativeId, ad, price, exp} = serverResponse.body; + if (!ad || !price) { + return []; + } + const {bidId, width, height} = request.data; + try { + return [{ + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + ad: ad + }]; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses) { + const {iframeEnabled, pixelEnabled} = syncOptions; + + if (iframeEnabled) { + return [{ + type: 'iframe', + url: '//static.cliipa.com/basev/sync/user_sync.html' + }]; + } + + if (pixelEnabled) { + const lookup = {}; + const syncs = []; + responses.forEach(response => { + const {body} = response; + const cookies = body ? body.cookies || [] : []; + cookies.forEach(cookie => { + switch (cookie.type) { + case INTERNAL_SYNC_TYPE.IFRAME: + break; + case INTERNAL_SYNC_TYPE.IMAGE: + if (pixelEnabled && !lookup[cookie.src]) { + syncs.push({ + type: EXTERNAL_SYNC_TYPE.IMAGE, + url: cookie.src + }); + } + break; + } + }); + }); + return syncs; + } + + return []; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/vidazooBidAdapter.md b/modules/vidazooBidAdapter.md new file mode 100644 index 00000000000..972a99a1445 --- /dev/null +++ b/modules/vidazooBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +**Module Name:** Vidazoo Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** server-dev@getintent.com + +# Description + +Module that connects to Vidazoo's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'vidazoo', + params: { + cId: '5a1c419d95fce900044c334e', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001 + } + } + ] + } +]; +``` diff --git a/modules/xendizBidAdapter.js b/modules/xendizBidAdapter.js new file mode 100644 index 00000000000..d40d3ada229 --- /dev/null +++ b/modules/xendizBidAdapter.js @@ -0,0 +1,102 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const BIDDER_CODE = 'xendiz'; +const PREBID_ENDPOINT = 'prebid.xendiz.com'; +const SYNC_ENDPOINT = 'https://advsync.com/xendiz/ssp/?pixel=1'; + +const buildURI = () => { + return `//${PREBID_ENDPOINT}/request`; +} + +const getDevice = () => { + const lang = navigator.language; + const width = window.screen.width; + const height = window.screen.height; + + return [lang, width, height]; +}; + +const buildItem = (req) => { + return [ + req.bidId, + req.params, + req.adUnitCode, + req.sizes.map(s => `${s[0]}x${s[1]}`) + ] +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!bid.params.pid; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests) { + const payload = { + id: bidRequests[0].auctionId, + items: bidRequests.map(buildItem), + device: getDevice(), + page: utils.getTopWindowUrl(), + dt: +new Date() + }; + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: buildURI(), + data: payloadString + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse) { + const bids = serverResponse.body.bids.map(bid => { + return { + requestId: bid.id, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + netRevenue: bid.netRevenue !== undefined ? bid.netRevenue : true, + dealId: bid.dealid, + currency: bid.cur || 'USD', + ttl: bid.exp || 900, + ad: bid.adm, + } + }); + + return bids; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_ENDPOINT + }]; + } + } +} + +registerBidder(spec); diff --git a/modules/xendizBidAdapter.md b/modules/xendizBidAdapter.md new file mode 100644 index 00000000000..4ecabe7070f --- /dev/null +++ b/modules/xendizBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: Xendiz Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hello@xendiz.com + +# Description + +Module that connects to Xendiz demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000' + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000', + ext: { + uid: '550e8400-e29b-41d4-a716-446655440000' + } + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js new file mode 100644 index 00000000000..17c205359de --- /dev/null +++ b/modules/yieldlabBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from 'src/utils' +import { registerBidder } from 'src/adapters/bidderFactory' +import find from 'core-js/library/fn/array/find' +import { VIDEO, BANNER } from 'src/mediaTypes' + +const ENDPOINT = 'https://ad.yieldlab.net' +const BIDDER_CODE = 'yieldlab' +const BID_RESPONSE_TTL_SEC = 600 +const CURRENCY_CODE = 'EUR' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.placementId && bid.params.adSize) { + return true + } + return false + }, + + /** + * This method should build correct URL + * @param validBidRequests + * @returns {{method: string, url: string}} + */ + buildRequests: function (validBidRequests) { + const placementIds = [] + const timestamp = Date.now() + + utils._each(validBidRequests, function (bid) { + placementIds.push(bid.params.placementId) + }) + + const placements = placementIds.join(',') + + return { + method: 'GET', + url: `${ENDPOINT}/yp/${placements}?ts=${timestamp}&json=true`, + validBidRequests: validBidRequests + } + }, + + /** + * Map ad values and pricing and stuff + * @param serverResponse + * @param originalBidRequest + */ + interpretResponse: function (serverResponse, originalBidRequest) { + const bidResponses = [] + const timestamp = Date.now() + + originalBidRequest.validBidRequests.forEach(function (bidRequest) { + if (!serverResponse.body) { + return + } + + let matchedBid = find(serverResponse.body, function (bidResponse) { + return bidRequest.params.placementId == bidResponse.id + }) + + if (matchedBid) { + const sizes = parseSize(bidRequest.params.adSize) + const bidResponse = { + requestId: bidRequest.bidId, + cpm: matchedBid.price / 100, + width: sizes[0], + height: sizes[1], + creativeId: '' + matchedBid.id, + dealId: matchedBid.did, + currency: CURRENCY_CODE, + netRevenue: true, + ttl: BID_RESPONSE_TTL_SEC, + referrer: '', + ad: `` + } + if (isVideo(bidRequest)) { + bidResponse.mediaType = VIDEO + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.accountId}/1x1?ts=${timestamp}` + } + + bidResponses.push(bidResponse) + } + }) + return bidResponses + } +}; + +/** + * Is this a video format? + * @param {String} format + * @returns {Boolean} + */ +function isVideo (format) { + return utils.deepAccess(format, 'mediaTypes.video') +} + +/** + * Expands a 'WxH' string as a 2-element [W, H] array + * @param {String} size + * @returns {Array} + */ +function parseSize (size) { + return size.split('x').map(Number) +} + +registerBidder(spec) diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md new file mode 100644 index 00000000000..0c9183aa4cd --- /dev/null +++ b/modules/yieldlabBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Yieldlab Bidder Adapter +Module Type: Bidder Adapter +Maintainer: api@platform-lunar.com +``` + +# Description + +Module that connects to Yieldlab's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: "test1", + sizes: [[800, 250]] + bids: [{ + bidder: "yieldlab", + params: { + placement: "4206978", + accountId: "2358365", + adSize: "800x250" + } + }] + }, { + code: "test2", + sizes: [[1, 1]], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [{ + bidder: "yieldlab", + params: { + placementId: "4207034", + accountId: "2358365", + adSize: "1x1" + } + }] + } + ]; +``` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js new file mode 100644 index 00000000000..1047224a3b7 --- /dev/null +++ b/modules/yieldmoBidAdapter.js @@ -0,0 +1,304 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'yieldmo'; +const CURRENCY = 'USD'; +const TIME_TO_LIVE = 300; +const NET_REVENUE = true; +const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; +const SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; +const localWindow = getTopWindow(); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner'], + /** + * Determines whether or not the given bid request is valid. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.adUnitCode && bid.bidId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests) { + let serverRequest = { + p: [], + page_url: utils.getTopWindowUrl(), + bust: new Date().getTime().toString(), + pr: utils.getTopWindowReferrer(), + scrd: localWindow.devicePixelRatio || 0, + dnt: getDNT(), + e: getEnvironment(), + description: getPageDescription(), + title: localWindow.document.title || '', + w: localWindow.innerWidth, + h: localWindow.innerHeight + }; + for (var request of bidRequests) { + serverRequest.p.push(addPlacement(request)); + } + serverRequest.p = '[' + serverRequest.p.toString() + ']'; + return { + method: 'GET', + url: SERVER_ENDPOINT, + data: serverRequest + } + }, + /** + * Makes Yieldmo Ad Server response compatible to Prebid specs + * @param serverResponse successful response from Ad Server + * @param bidderRequest original bidRequest + * @return {Bid[]} an array of bids + */ + interpretResponse: function(serverResponse) { + let bids = []; + let data = serverResponse.body; + if (data.length > 0) { + for (var response of data) { + if (response.cpm && response.cpm > 0) { + bids.push(createNewBid(response)); + } + } + } + return bids; + }, + getUserSync: function(syncOptions) { + if (trackingEnabled(syncOptions)) { + return [{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]; + } else { + return []; + } + } +} +registerBidder(spec); + +/*************************************** + * Helper Functions + ***************************************/ + +/** + * Adds placement information to array + * @param request bid request + */ +function addPlacement(request) { + const placementInfo = { + placement_id: request.adUnitCode, + callback_id: request.bidId, + sizes: request.sizes + } + if (request.params && request.params.placementId) { + placementInfo.ym_placement_id = request.params.placementId + } + return JSON.stringify(placementInfo); +} + +/** + * creates a new bid with response information + * @param response server response + */ +function createNewBid(response) { + return { + requestId: response['callback_id'], + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + ad: response.ad + }; +} + +/** + * Detects if tracking is allowed + * @returns false if dnt or if not iframe/pixel enabled + */ +function trackingEnabled(options) { + return (isIOS() && !getDNT() && options.iframeEnabled); +} + +/** + * Detects whether we're in iOS + * @returns true if in iOS + */ +function isIOS() { + return /iPhone|iPad|iPod/i.test(window.navigator.userAgent); +} + +/** + * Detects whether dnt is true + * @returns true if user enabled dnt + */ +function getDNT() { + return window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false; +} + +/** + * get page description + */ +function getPageDescription() { + if (document.querySelector('meta[name="description"]')) { + return document.querySelector('meta[name="description"]').getAttribute('content'); // Value of the description metadata from the publisher's page. + } else { + return ''; + } +} + +function getTopWindow() { + try { + return window.top; + } catch (e) { + return window; + } +} + +/*************************************** + * Detect Environment Helper Functions + ***************************************/ + +/** + * Represents a method for loading Yieldmo ads. Environments affect + * which formats can be loaded into the page + * Environments: + * CodeOnPage: 0, // div directly on publisher's page + * Amp: 1, // google Accelerate Mobile Pages ampproject.org + * Mraid = 2, // native loaded through the MRAID spec, without Yieldmo's SDK https://www.iab.net/media/file/IAB_MRAID_v2_FINAL.pdf + * Dfp: 4, // google doubleclick for publishers https://www.doubleclickbygoogle.com/ + * DfpInAmp: 5, // AMP page containing a DFP iframe + * SafeFrame: 10, + * DfpSafeFrame: 11,Sandboxed: 16, // An iframe that can't get to the top window. + * SuperSandboxed: 89, // An iframe without allow-same-origin + * Unknown: 90, // A default sandboxed implementation delivered by EnvironmentDispatch when all positive environment checks fail + */ + +/** + * Detects what environment we're in + * @returns Environment kind + */ +function getEnvironment() { + if (isSuperSandboxedIframe()) { + return 89; + } else if (isDfpInAmp()) { + return 5; + } else if (isDfp()) { + return 4; + } else if (isAmp()) { + return 1; + } else if (isDFPSafeFrame()) { + return 11; + } else if (isSafeFrame()) { + return 10; + } else if (isMraid()) { + return 2; + } else if (isCodeOnPage()) { + return 0; + } else if (isSandboxedIframe()) { + return 16; + } else { + return 90; + } +} + +/** + * @returns true if we are running on the top window at dispatch time + */ +function isCodeOnPage() { + return window === window.parent; +} + +/** + * @returns true if the environment is both DFP and AMP + */ +function isDfpInAmp() { + return isDfp() && isAmp(); +} + +/** + * @returns true if the window is in an iframe whose id and parent element id match DFP + */ +function isDfp() { + try { + const frameElement = window.frameElement; + const parentElement = window.frameElement.parentNode; + if (frameElement && parentElement) { + return frameElement.id.indexOf('google_ads_iframe') > -1 && parentElement.id.indexOf('google_ads_iframe') > -1; + } + return false; + } catch (e) { + return false; + } +} + +/** +* @returns true if there is an AMP context object +*/ +function isAmp() { + try { + const ampContext = window.context || window.parent.context; + if (ampContext && ampContext.pageViewId) { + return ampContext; + } + return false; + } catch (e) { + return false; + } +} + +/** + * @returns true if the environment is a SafeFrame. + */ +function isSafeFrame() { + return window.$sf && window.$sf.ext; +} + +/** + * @returns true if the environment is a dfp safe frame. + */ +function isDFPSafeFrame() { + if (window.location && window.location.href) { + const href = window.location.href; + return isSafeFrame() && href.indexOf('google') !== -1 && href.indexOf('safeframe') !== -1; + } + return false; +} + +/** + * Return true if we are in an iframe and can't access the top window. + */ +function isSandboxedIframe() { + return window.top !== window && !window.frameElement; +} + +/** + * Return true if we cannot document.write to a child iframe (this implies no allow-same-origin) + */ +function isSuperSandboxedIframe() { + const sacrificialIframe = window.document.createElement('iframe'); + try { + sacrificialIframe.setAttribute('style', 'display:none'); + window.document.body.appendChild(sacrificialIframe); + sacrificialIframe.contentWindow._testVar = true; + window.document.body.removeChild(sacrificialIframe); + return false; + } catch (e) { + window.document.body.removeChild(sacrificialIframe); + return true; + } +} + +/** + * @returns true if the window has the attribute identifying MRAID + */ +function isMraid() { + return !!(window.mraid); +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md new file mode 100644 index 00000000000..b93f72594bc --- /dev/null +++ b/modules/yieldmoBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Yieldmo Bid Adapter +Module Type: Bidder Adapter +Maintainer: melody@yieldmo.com +Note: Our ads will only render in mobile +``` + +# Description + +Connects to Yieldmo Ad Server for bids. + +Yieldmo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1779781193098233305' // string with at most 19 characters (may include numbers only) + } + }] + } +]; +``` \ No newline at end of file diff --git a/package.json b/package.json index b4ffaecda35..fdc8e5b7ea8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.1.1", + "version": "1.3.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 5577ffc6fca..b4a874a0736 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -234,7 +234,7 @@ exports.checkBidRequestSizes = (adUnits) => { if (mediaTypes && mediaTypes.video) { const video = mediaTypes.video; if (video.playerSize) { - if (Array.isArray(video.playerSize) && video.playerSize.length === 2 && Number.isInteger(video.playerSize[0]) && Number.isInteger(video.playerSize[1])) { + if (Array.isArray(video.playerSize) && video.playerSize.length === 2 && utils.isInteger(video.playerSize[0]) && utils.isInteger(video.playerSize[1])) { adUnit.sizes = video.playerSize; } else { utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [640, 480]. Removing invalid mediaTypes.video.playerSize property from request.'); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index ed3cc28db32..10315241c40 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -172,8 +172,14 @@ export function newBidder(spec) { // After all the responses have come back, call done() and // register any required usersync pixels. const responses = []; - function afterAllResponses() { - done(); + function afterAllResponses(bids) { + const videoBid = bids && bids[0] && bids[0].mediaType && bids[0].mediaType === 'video'; + const cacheEnabled = config.getConfig('cache.url'); + + // video bids with cache enabled need to be cached first before they are considered done + if (!(videoBid && cacheEnabled)) { + done(); + } registerSyncs(responses); } @@ -281,7 +287,7 @@ export function newBidder(spec) { addBidUsingRequestMap(bids); } } - onResponse(); + onResponse(bids); function addBidUsingRequestMap(bid) { const bidRequest = bidRequestMap[bid.requestId]; diff --git a/src/auction.js b/src/auction.js index 2a4cc128c2a..0af6458ac09 100644 --- a/src/auction.js +++ b/src/auction.js @@ -56,7 +56,6 @@ import { Renderer } from 'src/Renderer'; import { config } from 'src/config'; import { userSync } from 'src/userSync'; import { createHook } from 'src/hook'; -import { videoAdUnit } from 'src/video'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; @@ -153,20 +152,9 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) return innerBidRequestId === bidRequest.bidderRequestId; }); - const nonVideoBid = request.bids.filter(videoAdUnit).length === 0; - const videoBid = request.bids.filter(videoAdUnit).length > 0; - const videoBidNoCache = videoBid && !config.getConfig('cache.url'); - const videoBidWithCache = videoBid && config.getConfig('cache.url'); - - // video bids with cache enabled need to be cached first before saying they are done - if (!videoBidWithCache) { - request.doneCbCallCount += 1; - } - - // in case of mediaType video and prebidCache enabled, call bidsBackHandler after cache is stored. - if (nonVideoBid || videoBidNoCache) { - bidsBackAll() - } + // this is done for cache-enabled video bids in tryAddVideoBids, after the cache is stored + request.doneCbCallCount += 1; + bidsBackAll(); }, 1); } @@ -221,6 +209,44 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) } } +function doCallbacksIfTimedout(auctionInstance, bidResponse) { + if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { + auctionInstance.executeCallback(true); + } +} + +// Add a bid to the auction. +function addBidToAuction(auctionInstance, bidResponse) { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); + auctionInstance.addBidReceived(bidResponse); + + doCallbacksIfTimedout(auctionInstance, bidResponse); +} + +// Video bids may fail if the cache is down, or there's trouble on the network. +function tryAddVideoBid(auctionInstance, bidResponse, bidRequest, vastUrl) { + if (config.getConfig('cache.url')) { + store([bidResponse], function(error, cacheIds) { + if (error) { + utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); + + doCallbacksIfTimedout(auctionInstance, bidResponse); + } else { + bidResponse.videoCacheKey = cacheIds[0].uuid; + if (!vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll + bidRequest.doneCbCallCount += 1; + addBidToAuction(auctionInstance, bidResponse); + auctionInstance.bidsBackAll(); + } + }); + } else { + addBidToAuction(auctionInstance, bidResponse); + } +} + export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) { let auctionInstance = this; let bidRequests = auctionInstance.getBidRequests(); @@ -230,46 +256,9 @@ export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}); if (bidResponse.mediaType === 'video') { - tryAddVideoBid(bidResponse); + tryAddVideoBid(auctionInstance, bidResponse, bidRequest, bid.vastUrl); } else { - doCallbacksIfNeeded(); - addBidToAuction(bidResponse); - } - - function doCallbacksIfNeeded() { - if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { - auctionInstance.executeCallback(true); - } - } - - // Add a bid to the auction. - function addBidToAuction() { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); - auctionInstance.addBidReceived(bidResponse); - } - - // Video bids may fail if the cache is down, or there's trouble on the network. - function tryAddVideoBid(bidResponse) { - if (config.getConfig('cache.url')) { - store([bidResponse], function(error, cacheIds) { - if (error) { - utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - bidResponse.videoCacheKey = cacheIds[0].uuid; - if (!bid.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } - // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll - bidRequest.doneCbCallCount += 1; - addBidToAuction(bidResponse); - auctionInstance.bidsBackAll(); - } - doCallbacksIfNeeded(); - }); - } else { - addBidToAuction(bidResponse); - doCallbacksIfNeeded(); - } + addBidToAuction(auctionInstance, bidResponse); } }, 'addBidResponse'); @@ -299,7 +288,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) { const adUnitRenderer = bidRequest.bids && bidRequest.bids[0] && bidRequest.bids[0].renderer; - if (adUnitRenderer) { + if (adUnitRenderer && adUnitRenderer.url) { bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); bidObject.renderer.setRender(adUnitRenderer.render); } @@ -379,7 +368,13 @@ export function getStandardBidderSettings() { val: function (bidResponse) { return bidResponse.source; } - } + }, + { + key: 'hb_format', + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, ] } return bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; @@ -403,7 +398,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj) { } // set native key value targeting - if (custBidObj.native) { + if (custBidObj['native']) { keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } diff --git a/src/config.js b/src/config.js index 146c0f17a65..ff68fc7bfff 100644 --- a/src/config.js +++ b/src/config.js @@ -10,6 +10,7 @@ import { isValidPriceConfig } from './cpmBucketManager'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; +import { createHook } from 'src/hook'; const utils = require('./utils'); const DEFAULT_DEBUG = false; @@ -188,7 +189,7 @@ export function newConfig() { * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function */ - function setConfig(options) { + let setConfig = createHook('asyncSeries', function setConfig(options) { if (typeof options !== 'object') { utils.logError('setConfig options must be an object'); return; @@ -208,7 +209,7 @@ export function newConfig() { }); callSubscribers(topicalConfig); - } + }); /** * Sets configuration defaults which setConfig values can be applied on top of diff --git a/src/constants.json b/src/constants.json index e80c118ea83..9a2639ab83f 100644 --- a/src/constants.json +++ b/src/constants.json @@ -52,7 +52,8 @@ "hb_pb", "hb_size", "hb_deal", - "hb_source" + "hb_source", + "hb_format" ], "S2S" : { "SRC" : "s2s", diff --git a/src/prebid.js b/src/prebid.js index de74f2c4d7b..86fd678ee7e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -2,14 +2,13 @@ import { getGlobal } from './prebidGlobal'; import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, removeRequestId } from './utils'; -import { videoAdUnit, videoBidder, hasNonVideoBidder } from './video'; -import { nativeAdUnit, nativeBidder, hasNonNativeBidder } from './native'; import { listenMessagesFromCreative } from './secureCreatives'; import { userSync } from 'src/userSync.js'; import { loadScript } from './adloader'; import { config } from './config'; import { auctionManager } from './auctionManager'; import { targeting } from './targeting'; +import { createHook } from 'src/hook'; import includes from 'core-js/library/fn/array/includes'; var $$PREBID_GLOBAL$$ = getGlobal(); @@ -283,7 +282,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @param {Array} requestOptions.labels * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) { +$$PREBID_GLOBAL$$.requestBids = createHook('asyncSeries', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) { events.emit('requestBids'); const cbTimeout = timeout || config.getConfig('bidderTimeout'); adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; @@ -298,24 +297,34 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - // for video-enabled adUnits, only request bids for bidders that support video - adUnits.filter(videoAdUnit).filter(hasNonVideoBidder).forEach(adUnit => { - const nonVideoBidders = adUnit.bids - .filter(bid => !videoBidder(bid)) - .map(bid => bid.bidder); - - utils.logWarn(utils.unsupportedBidderMessage(adUnit, nonVideoBidders)); - adUnit.bids = adUnit.bids.filter(videoBidder); - }); - - // for native-enabled adUnits, only request bids for bidders that support native - adUnits.filter(nativeAdUnit).filter(hasNonNativeBidder).forEach(adUnit => { - const nonNativeBidders = adUnit.bids - .filter(bid => !nativeBidder(bid)) - .map(bid => bid.bidder); - - utils.logWarn(utils.unsupportedBidderMessage(adUnit, nonNativeBidders)); - adUnit.bids = adUnit.bids.filter(nativeBidder); + /* + * for a given adunit which supports a set of mediaTypes + * and a given bidder which supports a set of mediaTypes + * a bidder is eligible to participate on the adunit + * if it supports at least one of the mediaTypes on the adunit + */ + adUnits.forEach(adUnit => { + // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + + // get the bidder's mediaTypes + const bidders = adUnit.bids.map(bid => bid.bidder); + const bidderRegistry = adaptermanager.bidderRegistry; + + bidders.forEach(bidder => { + const adapter = bidderRegistry[bidder]; + const spec = adapter && adapter.getSpec && adapter.getSpec() + // banner is default if not specified in spec + const bidderMediaTypes = (spec && spec.supportedMediaTypes) || ['banner']; + + // check if the bidder's mediaTypes are not in the adUnit's mediaTypes + const bidderEligible = adUnitMediaTypes.some(type => includes(bidderMediaTypes, type)); + if (!bidderEligible) { + // drop the bidder from the ad unit if it's not compatible + utils.logWarn(utils.unsupportedBidderMessage(adUnit, bidder)); + adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); + } + }); }); if (!adUnits || adUnits.length === 0) { @@ -333,7 +342,8 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels}); auction.callBids(); -}; + return auction; +}); /** * diff --git a/src/utils.js b/src/utils.js index 2d185ebe4c6..0957b4e6c8f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -832,17 +832,16 @@ export function isSlotMatchingAdUnitCode(adUnitCode) { /** * Constructs warning message for when unsupported bidders are dropped from an adunit * @param {Object} adUnit ad unit from which the bidder is being dropped - * @param {Array} unSupportedBidders arrary of bidder codes that are not compatible with the adUnit + * @param {string} bidder bidder code that is not compatible with the adUnit * @return {string} warning message to display when condition is met */ -export function unsupportedBidderMessage(adUnit, unSupportedBidders) { - const mediaType = adUnit.mediaType || Object.keys(adUnit.mediaTypes).join(', '); - const plural = unSupportedBidders.length === 1 ? 'This bidder' : 'These bidders'; +export function unsupportedBidderMessage(adUnit, bidder) { + const mediaType = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}).join(', '); return ` ${adUnit.code} is a ${mediaType} ad unit - containing bidders that don't support ${mediaType}: ${unSupportedBidders.join(', ')}. - ${plural} won't fetch demand. + containing bidders that don't support ${mediaType}: ${bidder}. + This bidder won't fetch demand. `; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 3429087a24f..c73679c5b8c 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -37,6 +37,7 @@ describe('auctionmanager.js', function () { var size = '300x250'; var adId = '1adId'; var source = 'client'; + var mediatype = 'banner'; before(function () { bid.cpm = bidPriceCpm; @@ -54,6 +55,7 @@ describe('auctionmanager.js', function () { bid.bidderCode = bidderCode; bid.adId = adId; bid.source = source; + bid.mediaType = mediatype; }); it('No bidder level configuration defined - default', function () { @@ -62,7 +64,8 @@ describe('auctionmanager.js', function () { 'hb_adid': adId, 'hb_pb': bidPbMg, 'hb_size': size, - 'hb_source': source + 'hb_source': source, + 'hb_format': mediatype, }; var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); assert.deepEqual(response, expected); @@ -100,7 +103,13 @@ describe('auctionmanager.js', function () { val: function (bidResponse) { return bidResponse.source; } - } + }, + { + key: 'hb_format', + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, ] } @@ -111,7 +120,8 @@ describe('auctionmanager.js', function () { 'hb_adid': adId, 'hb_pb': bidPbHg, 'hb_size': size, - 'hb_source': source + 'hb_source': source, + 'hb_format': mediatype, }; var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); assert.deepEqual(response, expected); @@ -154,7 +164,8 @@ describe('auctionmanager.js', function () { 'hb_adid': adId, 'hb_pb': bidPbHg, 'hb_size': size, - 'hb_source': source + 'hb_source': source, + 'hb_format': mediatype, }; var response = getKeyValueTargetingPairs(bidderCode, bid); assert.deepEqual(response, expected); @@ -197,7 +208,8 @@ describe('auctionmanager.js', function () { 'hb_adid': adId, 'hb_pb': bidPbMg, 'hb_size': size, - 'hb_source': source + 'hb_source': source, + 'hb_format': mediatype, }; var response = getKeyValueTargetingPairs(bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); assert.deepEqual(response, expected); @@ -362,7 +374,8 @@ describe('auctionmanager.js', function () { 'hb_adid': adId, 'hb_pb': 5.57, 'hb_size': '300x250', - 'hb_source': source + 'hb_source': source, + 'hb_format': mediatype, }; var response = getKeyValueTargetingPairs(bidderCode, bid); assert.deepEqual(response, expected); @@ -453,6 +466,7 @@ describe('auctionmanager.js', function () { let auction; let ajaxStub; const BIDDER_CODE = 'sampleBidder'; + const BIDDER_CODE1 = 'sampleBidder1'; let makeRequestsStub; let bids = [{ 'ad': 'creative', @@ -643,57 +657,6 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, 'renderer.js'); }); }); - - describe('with auction timeout 20', () => { - let auction; - let adUnits; - let adUnitCodes; - let createAuctionStub; - let spec; - let getBidderRequestStub; - let eventsEmitSpy; - - beforeEach(() => { - adUnits = [{ - code: 'adUnit-code', - bids: [ - {bidder: BIDDER_CODE, params: {placementId: 'id'}}, - ] - }]; - adUnitCodes = ['adUnit-code']; - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 20}); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); - getBidderRequestStub = sinon.stub(utils, 'getBidderRequest'); - - let newBidRequest = Object.assign({}, bidRequests[0], {'start': 1000}); - getBidderRequestStub.returns(newBidRequest); - - spec = { - code: BIDDER_CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; - eventsEmitSpy = sinon.spy(events, 'emit'); - }); - - afterEach(() => { - auctionModule.newAuction.restore(); - utils.getBidderRequest.restore(); - events.emit.restore(); - }); - - it('should emit BID_TIMEOUT for timed out bids', () => { - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); - }); - }); }); describe('addBidResponse', () => { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 0b2d65e7db9..9a322821b13 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -2,7 +2,6 @@ const { userSync } = require('../../../src/userSync'); const { config } = require('../../../src/config'); const { expect } = require('chai'); - const { isBidRequestValid, buildRequests, @@ -44,78 +43,71 @@ describe('33acrossBidAdapter:', function () { }); describe('isBidRequestValid:', function () { - context('valid bid request:', function () { - it('returns true when bidder, params.siteId, params.productId are set', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } + it('returns true when valid bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID } + } - expect(isBidRequestValid(validBid)).to.be.true; - }) + expect(isBidRequestValid(validBid)).to.be.true; }); - context('valid test bid request:', function () { - it('returns true when bidder, params.site.id, params.productId are set', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - site: { - id: SITE_ID - }, - productId: PRODUCT_ID - } + it('returns true when valid test bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID, + test: 1 } + } - expect(isBidRequestValid(validBid)).to.be.true; - }); + expect(isBidRequestValid(validBid)).to.be.true; }); - context('invalid bid request:', function () { - it('returns false when bidder not set to "33across"', function () { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } + it('returns false when bidder not set to "33across"', function () { + const invalidBid = { + bidder: 'foo', + params: { + siteId: SITE_ID, + productId: PRODUCT_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params not set', function() { - const invalidBid = { - bidder: 'foo' - } + it('returns false when params not set', function() { + const invalidBid = { + bidder: 'foo' + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params.siteId or params.site.id not set', function() { - const invalidBid = { - bidder: 'foo', - params: { - productId: PRODUCT_ID - } + it('returns false when site ID is not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + productId: PRODUCT_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params.productId not set', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID - } + it('returns false when product ID not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + siteId: SITE_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; }); }); @@ -148,27 +140,76 @@ describe('33acrossBidAdapter:', function () { }, id: 'b1' }; + const serverRequest = { + 'method': 'POST', + 'url': END_POINT, + 'data': JSON.stringify(ttxRequest), + 'options': { + 'contentType': 'application/json', + 'withCredentials': false + } + } + const builtServerRequests = buildRequests(this.bidRequests); + expect(builtServerRequests).to.deep.equal([ serverRequest ]); + expect(builtServerRequests.length).to.equal(1); + }); + + it('returns corresponding test server requests for each valid bidRequest', function() { + this.sandbox.stub(config, 'getConfig', () => { + return { + 'url': 'https://foo.com/hb/' + } + }); + + const ttxRequest = { + imp: [ { + banner: { + format: [ + { + w: 300, + h: 250, + ext: { } + }, + { + w: 728, + h: 90, + ext: { } + } + ] + }, + ext: { + ttx: { + prod: PRODUCT_ID + } + } + } ], + site: { + id: SITE_ID + }, + id: 'b1' + }; const serverRequest = { method: 'POST', - url: END_POINT, + url: 'https://foo.com/hb/', data: JSON.stringify(ttxRequest), options: { contentType: 'application/json', withCredentials: false } - } + }; + const builtServerRequests = buildRequests(this.bidRequests); expect(builtServerRequests).to.deep.equal([ serverRequest ]); expect(builtServerRequests.length).to.equal(1); }); - it('returns corresponding test server requests for each valid bidRequest', function() { + it('returns corresponding test server requests for each valid test bidRequest', function() { this.sandbox.stub(config, 'getConfig', () => { return { 'url': 'https://foo.com/hb/' } }); - + this.bidRequests[0].params.test = 1; const ttxRequest = { imp: [ { banner: { @@ -176,12 +217,12 @@ describe('33acrossBidAdapter:', function () { { w: 300, h: 250, - ext: {} + ext: { } }, { w: 728, h: 90, - ext: {} + ext: { } } ] }, @@ -194,7 +235,8 @@ describe('33acrossBidAdapter:', function () { site: { id: SITE_ID }, - id: 'b1' + id: 'b1', + test: 1 }; const serverRequest = { method: 'POST', @@ -268,11 +310,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '1', adm: '

I am an ad

', - ext: { - rp: { - advid: 1 - } - }, + crid: 1, h: 250, w: 300, price: 0.0938 @@ -322,11 +360,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '1', adm: '

I am an ad

', - ext: { - rp: { - advid: 1 - } - }, + crid: 1, h: 250, w: 300, price: 0.0940 @@ -334,11 +368,7 @@ describe('33acrossBidAdapter:', function () { { id: '2', adm: '

I am an ad

', - ext: { - rp: { - advid: 2 - } - }, + crid: 2, h: 250, w: 300, price: 0.0938 @@ -349,11 +379,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '3', adm: '

I am an ad

', - ext: { - rp: { - advid: 3 - } - }, + crid: 3, h: 250, w: 300, price: 0.0938 @@ -373,7 +399,7 @@ describe('33acrossBidAdapter:', function () { creativeId: 1, currency: 'USD', netRevenue: true - } + }; expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 72f625d08c6..357e6e67f4d 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -22,7 +22,7 @@ describe('Adform adapter', () => { adxDomain: 'adx.adform.net' }; assert.isFalse(spec.isBidRequestValid(bid)); - }) + }); }); describe('buildRequests', () => { @@ -147,6 +147,7 @@ describe('Adform adapter', () => { bids = [ { adUnitCode: placementCode[0], + auctionId: '7aefb970-2045', bidId: '2a0cf4e', bidder: 'adform', bidderRequestId: '1ab8d9', @@ -154,29 +155,28 @@ describe('Adform adapter', () => { adxDomain: 'newDomain', tid: 45, placementCode: placementCode[0], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-4ca1' }, { adUnitCode: placementCode[1], + auctionId: '7aefb970-2045', bidId: '2a0cf5b', bidder: 'adform', bidderRequestId: '1ab8d9', params: params[1], placementCode: placementCode[1], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-4iuy' }, { adUnitCode: placementCode[2], + auctionId: '7aefb970-2045', bidId: '2a0cf6n', bidder: 'adform', bidderRequestId: '1ab8d9', params: params[2], placementCode: placementCode[2], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-7ev3' } diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js new file mode 100644 index 00000000000..7718d6fabac --- /dev/null +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -0,0 +1,333 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import {spec} from 'modules/adgenerationBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {NATIVE} from 'src/mediaTypes'; + +describe('AdgenerationAdapter', () => { + const adapter = newBidder(spec); + const ENDPOINT = ['http://api-test.scaleout.jp/adsv/v1', 'https://d.socdm.com/adsv/v1']; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adg', + 'params': { + id: '58278', // banner + } + }; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { // banner + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + { // native + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + }, + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + } + ]; + const data = { + banner: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&imark=1', + native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3' + }; + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[1]); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to debug ENDPOINT via GET', () => { + bidRequests[0].params.debug = true; + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[0]); + expect(request.method).to.equal('GET'); + }); + + it('should attache params to the banner request', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.equal(data.banner); + }); + + it('should attache params to the native request', () => { + const request = spec.buildRequests(bidRequests)[1]; + expect(request.data).to.equal(data.native); + }); + }); + + describe('interpretResponse', () => { + const bidRequests = { + bidRequest: { + bidder: 'adg', + params: { + id: '58278', // banner + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + } + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + } + }; + + const serverResponse = { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 + }, + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'], + link: { + clicktrackers: [ + 'https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' + }, + }, + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + }; + + const bidResponses = [ + { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + referrer: utils.getTopWindowUrl(), + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + clickUrl: 'https://supership.jp', + clickTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'] + }, + mediaType: NATIVE + } + ]; + + it('no bid responses', () => { + const result = spec.interpretResponse({body: serverResponse}, bidRequests); + expect(result.length).to.equal(0); + }); + + it('handles native responses', () => { + serverResponse.results = [{ad: 'Creative<\/body>'}]; + const result = spec.interpretResponse({body: serverResponse}, bidRequests)[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.dealId).to.equal(bidResponses[0].dealId); + expect(result.currency).to.equal(bidResponses[0].currency); + expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); + expect(result.ttl).to.equal(bidResponses[0].ttl); + expect(result.referrer).to.equal(bidResponses[0].referrer); + expect(result.native.title).to.equal(bidResponses[0].native.title); + expect(result.native.image.url).to.equal(bidResponses[0].native.image.url); + expect(result.native.image.height).to.equal(bidResponses[0].native.image.height); + expect(result.native.image.width).to.equal(bidResponses[0].native.image.width); + expect(result.native.icon.url).to.equal(bidResponses[0].native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses[0].native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses[0].native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses[0].native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses[0].native.body); + expect(result.native.cta).to.equal(bidResponses[0].native.cta); + expect(result.native.clickUrl).to.equal(bidResponses[0].native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses[0].native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses[0].native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses[0].mediaType); + }); + + it('handles banner responses', () => { + serverResponse.results = [{ad: '↵ ↵ ↵ ↵ ↵
↵ '}]; + delete serverResponse.native_ad; + delete serverResponse.mediaType; + delete bidRequests.bidRequest.mediaTypes; + const result = spec.interpretResponse({body: serverResponse}, bidRequests)[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.dealId).to.equal(bidResponses[0].dealId); + expect(result.currency).to.equal(bidResponses[0].currency); + expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); + expect(result.ttl).to.equal(bidResponses[0].ttl); + expect(result.referrer).to.equal(bidResponses[0].referrer); + expect(result.ad).to.equal(bidResponses[0].ad); + }); + }); +}); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index 1907cfb14b8..37a5b46a8a2 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -9,6 +9,7 @@ describe('Adomik Prebid Analytic', function () { beforeEach(() => { sinon.spy(adomikAnalytics, 'track'); sinon.spy(adomikAnalytics, 'sendTypedEvent'); + sinon.spy(adomikAnalytics, 'sendWonEvent'); }); afterEach(() => { @@ -51,25 +52,21 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: '', - timeouted: false, - timeout: 0, + timeouted: false }); - // Step 1: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test', timeout: 3000}); + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: 'test-test-test', - timeouted: false, - timeout: 3000, + timeouted: false }); - // Step 2: Send bid requested event + // Step 3: Send bid requested event events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid] }); expect(adomikAnalytics.bucketEvents.length).to.equal(1); @@ -81,7 +78,7 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 3: Send bid response event + // Step 4: Send bid response event events.emit(constants.EVENTS.BID_RESPONSE, bid); expect(adomikAnalytics.bucketEvents.length).to.equal(2); @@ -102,29 +99,23 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 4: Send bid won event + // Step 5: Send bid won event events.emit(constants.EVENTS.BID_WON, bid); - expect(adomikAnalytics.bucketEvents.length).to.equal(3); - expect(adomikAnalytics.bucketEvents[2]).to.deep.equal({ - type: 'winner', - event: { - id: '1234', - placementCode: '0000', - } - }); + expect(adomikAnalytics.bucketEvents.length).to.equal(2); - // Step 5: Send bid timeout event + // Step 6: Send bid timeout event events.emit(constants.EVENTS.BID_TIMEOUT, {}); expect(adomikAnalytics.currentContext.timeouted).to.equal(true); - // Step 6: Send auction end event + // Step 7: Send auction end event var clock = sinon.useFakeTimers(); events.emit(constants.EVENTS.AUCTION_END, {}); setTimeout(function() { sinon.assert.callCount(adomikAnalytics.sendTypedEvent, 1); + sinon.assert.callCount(adomikAnalytics.sendWonEvent, 1); done(); }, 3000); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c95fe18c688..560fcf40acc 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/appnexusBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid'; @@ -76,6 +77,30 @@ describe('AppNexusAdapter', () => { }); }); + it('should populate the ad_types array on all requests', () => { + ['banner', 'video', 'native'].forEach(type => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + }); + }); + + it('should populate the ad_types array on outstream requests', () => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + }); + it('sends bid request to ENDPOINT via POST', () => { const request = spec.buildRequests(bidRequests); expect(request.url).to.equal(ENDPOINT); @@ -364,12 +389,11 @@ describe('AppNexusAdapter', () => { let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('descriptionUrl'); expect(result[0]).to.have.property('mediaType', 'video'); }); it('handles native responses', () => { - let response1 = Object.assign({}, response); + let response1 = deepClone(response); response1.tags[0].ads[0].ad_type = 'native'; response1.tags[0].ads[0].rtb.native = { 'title': 'Native Creative', @@ -401,5 +425,26 @@ describe('AppNexusAdapter', () => { expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('http://cdn.adnxs.com/img.png'); }); + + it('supports configuring outstream renderers', () => { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + renderer: { + options: { + adText: 'configured' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); }); }); diff --git a/test/spec/modules/atomxBidAdapter_spec.js b/test/spec/modules/atomxBidAdapter_spec.js new file mode 100644 index 00000000000..fdbb01a1838 --- /dev/null +++ b/test/spec/modules/atomxBidAdapter_spec.js @@ -0,0 +1,119 @@ +import { expect } from 'chai'; +import { spec } from 'modules/atomxBidAdapter'; + +describe('atomxAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with id param', () => { + expect(spec.isBidRequestValid({ + bidder: 'atomx', + params: { + id: 1234, + }, + })).to.equal(true); + }); + + it('bidRequest with no id param', () => { + expect(spec.isBidRequestValid({ + bidder: 'atomx', + params: { + }, + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'atomx', + 'params': { + 'id': '123' + }, + 'adUnitCode': 'aaa', + 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + 'sizes': [300, 250], + 'bidId': '1abgs362e0x48a8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + 'bidder': 'atomx', + 'params': { + 'id': '456', + }, + 'adUnitCode': 'bbb', + 'transactionId': '193995b4-7122-4739-959b-2463282a138b', + 'sizes': [[800, 600]], + 'bidId': '22aidtbx5eabd9', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest url', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp('p\\.ato\\.mx/placement')); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.id).to.equal('123'); + expect(requests[0].data.size).to.equal('300x250'); + expect(requests[0].data.prebid).to.equal('1abgs362e0x48a8'); + expect(requests[1].data.id).to.equal('456'); + expect(requests[1].data.size).to.equal('800x600'); + expect(requests[1].data.prebid).to.equal('22aidtbx5eabd9'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://p.ato.mx/placement', + 'data': { + 'v': 12, + 'id': '123', + 'size': '300x250', + 'prebid': '22aidtbx5eabd9', + 'b': 0, + 'h': '7t3y9', + 'type': 'javascript', + 'screen': '800x600x32', + 'timezone': 0, + 'domain': 'https://example.com', + 'r': '', + } + }; + + const bidResponse = { + body: { + 'cpm': 0.00009, + 'width': 300, + 'height': 250, + 'url': 'http://atomx.com', + 'creative_id': 456, + 'code': '22aidtbx5eabd9', + }, + headers: {} + }; + + it('result is correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.equal('22aidtbx5eabd9'); + expect(result[0].cpm).to.equal(0.00009 * 1000); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(456); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].adUrl).to.equal('http://atomx.com'); + }); + }); +}); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js index b153a58eeb0..6b21eb459d4 100644 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js @@ -25,7 +25,7 @@ describe('AudienceNetwork adapter', () => { expect(code).to.equal(bidder); }); it('supportedMediaTypes', () => { - expect(supportedMediaTypes).to.deep.equal(['video']); + expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); }); it('isBidRequestValid', () => { expect(isBidRequestValid).to.be.a('function'); @@ -390,5 +390,43 @@ describe('AudienceNetwork adapter', () => { expect(bidResponseNative.height).to.equal(250); expect(bidResponseNative.ad).to.contain(`placementid:'${nativePlacementId}',format:'native',bidid:'${nativeBidId}'`); }); + + it('mixture of valid native bid and error in response', () => { + const [bidResponse] = interpretResponse({ + body: { + errors: ['test-error-message'], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } + } + }, { + adformats: ['native'], + requestIds: [requestId], + sizes: [[300, 250]] + }); + + expect(bidResponse.cpm).to.equal(1.23); + expect(bidResponse.requestId).to.equal(requestId); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse.ad) + .to.contain(`placementid:'${placementId}',format:'native',bidid:'test-bid-id'`, 'ad missing parameters') + .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') + .and.to.contain('
', 'ad missing native container'); + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + + expect(bidResponse.hb_bidder).to.equal('fan'); + expect(bidResponse.fb_bidid).to.equal('test-bid-id'); + expect(bidResponse.fb_format).to.equal('native'); + expect(bidResponse.fb_placementid).to.equal(placementId); + }); }); }); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js new file mode 100644 index 00000000000..0517668ea0d --- /dev/null +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai'; +import { c1xAdapter } from 'modules/c1xBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = 'https://ht.c1exchange.com/ht'; +const BIDDER_CODE = 'c1x'; + +describe('C1XAdapter', () => { + const adapter = newBidder(c1xAdapter); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': BIDDER_CODE, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'siteId': '9999' + } + }; + + it('should return true when required params are passed', () => { + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': null + }; + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': '9999' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + const parseRequest = (data) => { + const parsedData = '{"' + data.replace(/=|&/g, (foundChar) => { + if (foundChar == '=') return '":"'; + else if (foundChar == '&') return '","'; + }) + '"}' + return parsedData; + }; + + it('sends bid request to ENDPOINT via GET', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should generate correct bid Id tag', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.bids[0].adUnitCode).to.equal('adunit-code'); + expect(request.bids[0].bidId).to.equal('30b31c1838de1e'); + }); + + it('should convert params to proper form and attach to request', () => { + const request = c1xAdapter.buildRequests(bidRequests); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.adunits).to.equal('1'); + expect(payloadObj.a1s).to.equal('300x250,300x600'); + expect(payloadObj.a1).to.equal('adunit-code'); + expect(payloadObj.site).to.equal('9999'); + }); + + it('should convert floor price to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'floorPriceMap': { + '300x250': 4.35 + } + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.a1p).to.equal('4.35'); + }); + + it('should convert pageurl to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'pageurl': 'http://c1exchange.com/' + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.pageurl).to.equal('http://c1exchange.com/'); + }); + }); + + describe('interpretResponse', () => { + let response = { + 'bid': true, + 'cpm': 1.5, + 'ad': '', + 'width': 300, + 'height': 250, + 'crid': '8888', + 'adId': 'c1x-test', + 'bidType': 'GROSS_BID' + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + width: 300, + height: 250, + cpm: 1.5, + ad: '', + creativeId: '8888', + currency: 'USD', + ttl: 300, + netRevenue: false, + requestId: 'yyyy' + } + ]; + let bidderRequest = {}; + bidderRequest.bids = [ + { adUnitCode: 'c1x-test', + bidId: 'yyyy' } + ]; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = { + bid: false, + adId: 'c1x-test' + }; + let bidderRequest = {}; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js new file mode 100644 index 00000000000..e14d2f27b42 --- /dev/null +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -0,0 +1,119 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/colossussspBidAdapter'; + +describe('ColossussspAdapter', () => { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'colossusssp', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: 0 + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + describe('isBidRequestValid', () => { + it('Should return true when placement_id can be cast to a number, and when at least one of the sizes passed is allowed', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placement_id is not a number', () => { + bid.params.placement_id = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('Should return false when the sizes are not allowed', () => { + bid.sizes = [[1, 1]]; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//colossusssp.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'bidId', 'sizes'); + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', () => { + let resObject = { + body: [ { + requestId: '123', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', () => { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', () => { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', () => { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', () => { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('//colossusssp.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index c99cad473a3..9f7cb3ff17d 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -116,8 +116,8 @@ describe('Conversant adapter tests', function() { expect(spec.code).to.equal('conversant'); expect(spec.aliases).to.be.an('array').with.lengthOf(1); expect(spec.aliases[0]).to.equal('cnvr'); - expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(1); - expect(spec.supportedMediaTypes[0]).to.equal('video'); + expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(2); + expect(spec.supportedMediaTypes[1]).to.equal('video'); }); it('Verify user syncs', function() { diff --git a/test/spec/modules/danmarketplaceBidAdapter_spec.js b/test/spec/modules/danmarketplaceBidAdapter_spec.js new file mode 100644 index 00000000000..ee7f844ae98 --- /dev/null +++ b/test/spec/modules/danmarketplaceBidAdapter_spec.js @@ -0,0 +1,285 @@ +import { expect } from 'chai'; +import { spec } from 'modules/danmarketplaceBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('DAN_Marketplace Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 4, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 5, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 6, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 5, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '7' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '8' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js new file mode 100644 index 00000000000..a2583d79dc2 --- /dev/null +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -0,0 +1,350 @@ +import { expect } from 'chai'; +import { spec } from 'modules/eplanningBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('E-Planning Adapter', () => { + const adapter = newBidder('spec'); + const CI = '12345'; + const ADUNIT_CODE = 'adunit-code'; + const ADUNIT_CODE2 = 'adunit-code-dos'; + const CLEAN_ADUNIT_CODE2 = 'adunitcodedos'; + const CLEAN_ADUNIT_CODE = 'adunitcode'; + const BID_ID = '123456789'; + const BID_ID2 = '987654321'; + const CPM = 1.3; + const W = '300'; + const H = '250'; + const ADM = '
This is an ad
'; + const I_ID = '7854abc56248f873'; + const CRID = '1234567890'; + const TEST_ISV = 'leles.e-planning.net'; + const validBid = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + }; + const testBid = { + 'bidder': 'eplanning', + 'params': { + 't': 1, + 'isv': TEST_ISV + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const invalidBid = { + 'bidder': 'eplanning', + 'params': { + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + }; + const response = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithTwoAdunits = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }] + }, { + 'k': CLEAN_ADUNIT_CODE2, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }, + ], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoAd = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': 'spname', + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoSpace = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when bid has ci parameter', () => { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid does not have ci parameter and is not a test bid', () => { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true when bid does not have ci parameter but is a test bid', () => { + expect(spec.isBidRequestValid(testBid)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [validBid]; + + it('should create the url correctly', () => { + const url = spec.buildRequests(bidRequests).url; + expect(url).to.equal('//ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + }); + + it('should return GET method', () => { + const method = spec.buildRequests(bidRequests).method; + expect(method).to.equal('GET'); + }); + + it('should return r parameter with value pbjs', () => { + const r = spec.buildRequests(bidRequests).data.r; + expect(r).to.equal('pbjs'); + }); + + it('should return pbv parameter with value prebid version', () => { + const pbv = spec.buildRequests(bidRequests).data.pbv; + expect(pbv).to.equal('$prebid.version$'); + }); + + it('should return e parameter with value according to the adunit sizes', () => { + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600'); + }); + + it('should return correct e parameter with more than one adunit', () => { + const NEW_CODE = ADUNIT_CODE + '2'; + const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': NEW_CODE, + 'sizes': [[100, 100]], + }; + bidRequests.push(anotherBid); + + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); + }); + + it('should return correct e parameter when the adunit has no size', () => { + const noSizeBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + }; + + const e = spec.buildRequests([noSizeBid]).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':1x1'); + }); + + it('should return ur parameter with current window url', () => { + const ur = spec.buildRequests(bidRequests).data.ur; + expect(ur).to.equal(utils.getTopWindowUrl()); + }); + + it('should return fr parameter when there is a referrer', () => { + const referrer = 'thisisafakereferrer'; + const stubGetReferrer = sinon.stub(utils, 'getTopWindowReferrer').returns(referrer); + after(() => stubGetReferrer.restore()); + + const fr = spec.buildRequests(bidRequests).data.fr; + expect(fr).to.equal(referrer); + }); + + it('should return the testing url when the request has the t parameter', () => { + const url = spec.buildRequests([testBid]).url; + const expectedUrl = '//' + TEST_ISV + '/layers/t_pbjs_2.json'; + expect(url).to.equal(expectedUrl); + }); + + it('should return the parameter ncb with value 1', () => { + const ncb = spec.buildRequests(bidRequests).data.ncb; + expect(ncb).to.equal('1'); + }); + }); + + describe('interpretResponse', () => { + it('should return an empty array when there is no ads in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoAd); + expect(bidResponses).to.be.empty; + }); + + it('should return an empty array when there is no spaces in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoSpace); + expect(bidResponses).to.be.empty; + }); + + it('should correctly map the parameters in the response', () => { + const bidResponse = spec.interpretResponse(response, { adUnitToBidId: { [CLEAN_ADUNIT_CODE]: BID_ID } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ad: ADM, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); + }); + + describe('getUserSyncs', () => { + const sOptionsAllEnabled = { + pixelEnabled: true, + iframeEnabled: true + }; + const sOptionsAllDisabled = { + pixelEnabled: false, + iframeEnabled: false + }; + const sOptionsOnlyPixel = { + pixelEnabled: true, + iframeEnabled: false + }; + const sOptionsOnlyIframe = { + pixelEnabled: false, + iframeEnabled: true + }; + + it('should return an empty array if the response has no syncs', () => { + const noSyncsResponse = { cs: [] }; + const syncs = spec.getUserSyncs(sOptionsAllEnabled, [noSyncsResponse]); + expect(syncs).to.be.empty; + }); + + it('should return an empty array if there is no sync options enabled', () => { + const syncs = spec.getUserSyncs(sOptionsAllDisabled, [response]); + expect(syncs).to.be.empty; + }); + + it('should only return pixels if iframe is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyPixel, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('image')); + }); + + it('should only return iframes if pixel is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyIframe, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('iframe')); + }); + }); + + describe('adUnits mapping to bidId', () => { + it('should correctly map the bidId to the adunit', () => { + const requests = spec.buildRequests([validBid, validBid2]); + const responses = spec.interpretResponse(responseWithTwoAdunits, requests); + expect(responses[0].requestId).to.equal(BID_ID); + expect(responses[1].requestId).to.equal(BID_ID2); + }); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js new file mode 100644 index 00000000000..30627d4d12d --- /dev/null +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -0,0 +1,138 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/gumgumBidAdapter'; + +const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; + +describe('gumgumAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gumgum', + 'params': { + 'inScreen': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'gumgum', + 'params': { + 'inSlot': '9' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + } + ]; + + it('sends bid request to ENDPOINT via GET', () => { + const requests = spec.buildRequests(bidRequests); + const request = requests[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + expect(request.id).to.equal('30b31c1838de1e'); + }); + }) + + describe('interpretResponse', () => { + let serverResponse = { + 'ad': { + 'id': 29593, + 'width': 300, + 'height': 250, + 'ipd': 2000, + 'markup': '

I am an ad

', + 'ii': true, + 'du': null, + 'price': 0, + 'zi': 0, + 'impurl': 'http://g2.gumgum.com/ad/view', + 'clsurl': 'http://g2.gumgum.com/ad/close' + }, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let bidRequest = { + id: 12345, + sizes: [[300, 250]], + url: ENDPOINT, + method: 'GET', + pi: 3 + } + + it('should get correct bid response', () => { + let expectedResponse = { + 'ad': '

I am an ad

', + 'cpm': 0, + 'creativeId': 29593, + 'currency': 'USD', + 'height': '250', + 'netRevenue': true, + 'requestId': 12345, + 'width': '300', + // dealId: DEAL_ID, + // referrer: REFERER, + ttl: 60 + }; + expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); + }); + + it('handles nobid responses', () => { + let response = { + 'ad': {}, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let result = spec.interpretResponse({ body: response }, bidRequest); + expect(result.length).to.equal(0); + }); + }) +}); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 3f93a62e850..2725a6ad101 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter'; +import { config } from 'src/config'; import { userSync } from 'src/userSync'; describe('Improve Digital Adapter Tests', function () { @@ -129,6 +130,15 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.imp[0].banner).to.deep.equal(size); }); + it('should add currency', () => { + const bidRequest = Object.assign({}, simpleBidRequest); + const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + expect(params.bid_request.imp[0].currency).to.equal('JPY'); + getConfigStub.restore(); + }); + it('should return 2 requests', () => { const requests = spec.buildRequests([ simpleBidRequest, diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js new file mode 100644 index 00000000000..ee5064dca5a --- /dev/null +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -0,0 +1,253 @@ +import { expect } from 'chai'; +import { spec } from 'modules/inskinBidAdapter'; + +var bidFactory = require('src/bidfactory.js'); + +const ENDPOINT = 'https://mfad.inskinad.com/api/v2'; + +const REQUEST = { + 'bidderCode': 'inskin', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d', + 'bidderRequestId': '109f2a181342a9', + 'bidRequest': [{ + 'bidder': 'inskin', + 'params': { + 'networkId': 9874, + 'siteId': 730181 + }, + 'placementCode': 'div-gpt-ad-1487778092495-0', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '2b0f82502298c9', + 'bidderRequestId': '109f2a181342a9', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' + }, + { + 'bidder': 'inskin', + 'params': { + 'networkId': 9874, + 'siteId': 730181 + }, + 'placementCode': 'div-gpt-ad-1487778092495-0', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '123', + 'bidderRequestId': '109f2a181342a9', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'decisions': { + '2b0f82502298c9': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://mfad.inskinad.com/r', + 'impressionUrl': 'https://mfad.inskinad.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + }, + '123': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://mfad.inskinad.com/r', + 'impressionUrl': 'https://mfad.inskinad.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + } + } + } +}; + +describe('InSkin BidAdapter', () => { + let bidRequests; + let adapter = spec; + + beforeEach(() => { + bidRequests = [ + { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx' + }, + placementCode: 'header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); + + describe('bid request validation', () => { + it('should accept valid bid requests', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should accept valid bid requests with extra fields', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx', + zoneId: 'xxxxx' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reject bid requests without siteId', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reject bid requests without networkId', () => { + let bid = { + bidder: 'inskin', + params: { + siteId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests validation', () => { + it('creates request data', () => { + let request = spec.buildRequests(bidRequests); + + expect(request).to.exist.and.to.be.a('object'); + }); + + it('request to inskin should contain a url', () => { + let request = spec.buildRequests(bidRequests); + + expect(request.url).to.have.string('inskinad.com'); + }); + + it('requires valid bids to make request', () => { + let request = spec.buildRequests([]); + expect(request.bidRequest).to.be.empty; + }); + + it('sends bid request to ENDPOINT via POST', () => { + let request = spec.buildRequests(bidRequests); + + expect(request.method).to.have.string('POST'); + }); + }); + describe('interpretResponse validation', () => { + it('response should have valid bidderCode', () => { + let bidRequest = spec.buildRequests(REQUEST.bidRequest); + let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); + + expect(bid.bidderCode).to.equal('inskin'); + }); + + it('response should include objects for all bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + + expect(bids.length).to.equal(2); + }); + + it('registers bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + bids.forEach(b => { + expect(b).to.have.property('cpm'); + expect(b.cpm).to.be.above(0); + expect(b).to.have.property('requestId'); + expect(b).to.have.property('cpm'); + expect(b).to.have.property('width'); + expect(b).to.have.property('height'); + expect(b).to.have.property('ad'); + expect(b).to.have.property('currency', 'USD'); + expect(b).to.have.property('creativeId'); + expect(b).to.have.property('ttl', 360); + expect(b).to.have.property('netRevenue', true); + expect(b).to.have.property('referrer'); + }); + }); + + it('handles nobid responses', () => { + let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) + let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + + it('handles no server response', () => { + let bids = spec.interpretResponse(null, REQUEST); + + expect(bids).to.be.empty; + }); + }); + describe('getUserSyncs', () => { + let syncOptions = {'iframeEnabled': true}; + + it('handles empty sync options', () => { + let opts = spec.getUserSyncs({}); + + expect(opts).to.be.empty; + }); + + it('should always return empty array', () => { + let opts = spec.getUserSyncs(syncOptions); + + expect(opts).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js new file mode 100644 index 00000000000..e2cd4df9a07 --- /dev/null +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -0,0 +1,178 @@ +import {expect} from 'chai'; +import {spec} from 'modules/mantisBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('MantisAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('domain override', () => { + window.mantis_domain = 'http://foo'; + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('http://foo'); + + delete window.mantis_domain; + }); + + it('standard request', () => { + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('property=10433394'); + expect(request.url).to.include('bids[0][bidId]=30b31c1838de1e'); + expect(request.url).to.include('bids[0][config][zone]=zone'); + expect(request.url).to.include('bids[0][sizes][0][width]=300'); + expect(request.url).to.include('bids[0][sizes][0][height]=250'); + expect(request.url).to.include('bids[0][sizes][1][width]=300'); + expect(request.url).to.include('bids[0][sizes][1][height]=600'); + }); + + it('use window uuid', () => { + window.mantis_uuid = 'foo'; + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('uuid=foo'); + + delete window.mantis_uuid; + }); + + it('use storage uuid', () => { + window.localStorage.setItem('mantis:uuid', 'bar'); + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('uuid=bar'); + + window.localStorage.removeItem('mantis:uuid'); + }); + + it('detect amp', () => { + var oldContext = window.context; + + window.context = {}; + window.context.tagName = 'AMP-AD'; + window.context.canonicalUrl = 'foo'; + + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.include('amp=true'); + expect(request.url).to.include('url=foo'); + + delete window.context.tagName; + delete window.context.canonicalUrl; + + window.context = oldContext; + }); + }); + + describe('getUserSyncs', () => { + it('iframe', () => { + let result = spec.getUserSyncs({ + iframeEnabled: true + }); + + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/iframe'); + }); + + it('pixel', () => { + let result = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/pixel'); + }); + }); + + describe('interpretResponse', () => { + it('display ads returned', () => { + let response = { + body: { + ads: [ + { + bid: 'bid', + cpm: 1, + view: 'view', + width: 300, + height: 250, + html: '' + } + ] + } + }; + + let expectedResponse = [ + { + requestId: 'bid', + cpm: 1, + width: 300, + height: 250, + ttl: 86400, + ad: '', + creativeId: 'view', + netRevenue: true, + currency: 'USD' + } + ]; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('no ads returned', () => { + let response = { + body: { + ads: [] + } + }; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js new file mode 100644 index 00000000000..db543eda433 --- /dev/null +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -0,0 +1,329 @@ +import {expect} from 'chai'; +import {spec} from 'modules/medianetBidAdapter'; + +let VALID_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [[300, 250]], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }], + VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'bidfloor': 'abcdef', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [[300, 250]], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }], + VALID_PAYLOAD = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id' + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0' + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }] + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123' + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }] + }] + }, + VALID_PARAMS = { + bidder: 'medianet', + params: { + cid: '8CUV090' + } + }, + PARAMS_WITHOUT_CID = { + bidder: 'medianet', + params: {} + }, + PARAMS_WITH_INTEGER_CID = { + bidder: 'medianet', + params: { + cid: 8867587 + } + }, + PARAMS_WITH_EMPTY_CID = { + bidder: 'medianet', + params: { + cid: '' + } + }, + SYNC_OPTIONS_BOTH_ENABLED = { + iframeEnabled: true, + pixelEnabled: true, + }, + SYNC_OPTIONS_PIXEL_ENABLED = { + iframeEnabled: false, + pixelEnabled: true, + }, + SYNC_OPTIONS_IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }, + SERVER_CSYNC_RESPONSE = [{ + body: { + ext: { + csUrl: [{ + type: 'iframe', + url: 'iframe-url' + }, { + type: 'image', + url: 'pixel-url' + }] + } + } + }], + ENABLED_SYNC_IFRAME = [{ + type: 'iframe', + url: 'iframe-url' + }], + ENABLED_SYNC_PIXEL = [{ + type: 'image', + url: 'pixel-url' + }], + SERVER_RESPONSE_CPM_MISSING = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_CPM_ZERO = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.0 + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_NOBID = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': true, + 'requestId': '3a62cf7a853f84', + 'width': 0, + 'height': 0, + 'ttl': 0, + 'netRevenue': false + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + BID_REQUEST_SIZE_AS_1DARRAY = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [300, 250], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [300, 251], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d' + }]; + +describe('Media.net bid adapter', () => { + describe('isBidRequestValid', () => { + it('should accept valid bid params', () => { + let isValid = spec.isBidRequestValid(VALID_PARAMS); + expect(isValid).to.equal(true); + }); + + it('should reject bid if cid is not present', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITHOUT_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is not a string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_INTEGER_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is a empty string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_EMPTY_CID); + expect(isValid).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should build valid payload on bid', () => { + let requestObj = spec.buildRequests(VALID_BID_REQUEST); + expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should accept size as a one dimensional array', () => { + let bidReq = spec.buildRequests(BID_REQUEST_SIZE_AS_1DARRAY); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should ignore bidfloor if not a valid number', () => { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD); + }); + }); + + describe('getUserSyncs', () => { + it('should exclude iframe syncs if iframe is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_PIXEL_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_PIXEL); + }); + + it('should exclude pixel syncs if pixel is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_IFRAME_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + + it('should choose iframe sync urls if both sync options are enabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_BOTH_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + }); + + describe('interpretResponse', () => { + it('should not push bid response if cpm missing', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_MISSING, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push bid response if cpm 0', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_ZERO, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push response if no-bid', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBID, []); + expect(bids).to.deep.equal(validBids) + }); + }); +}); diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js index 4b498c88982..14927d929d3 100644 --- a/test/spec/modules/nanointeractiveBidAdapter_spec.js +++ b/test/spec/modules/nanointeractiveBidAdapter_spec.js @@ -2,8 +2,7 @@ import { expect } from 'chai'; import * as utils from 'src/utils'; import { - ALG, - BIDDER_CODE, CATEGORY, DATA_PARTNER_ID, DATA_PARTNER_PIXEL_ID, ENGINE_BASE_URL, NQ, NQ_NAME, SECURITY, + BIDDER_CODE, CATEGORY, DATA_PARTNER_PIXEL_ID, ENGINE_BASE_URL, NQ, NQ_NAME, spec } from '../../../modules/nanointeractiveBidAdapter'; @@ -20,10 +19,7 @@ describe('nanointeractive adapter tests', function () { bidder: BIDDER_CODE, params: (function () { return { - [SECURITY]: isValid === true ? 'sec1' : null, - [DATA_PARTNER_ID]: 'dpid1', - [DATA_PARTNER_PIXEL_ID]: 'pid1', - [ALG]: 'ihr', + [DATA_PARTNER_PIXEL_ID]: isValid === true ? 'pid1' : null, [NQ]: SEARCH_QUERY, [NQ_NAME]: null, [CATEGORY]: null, @@ -38,11 +34,8 @@ describe('nanointeractive adapter tests', function () { } } - const SINGlE_BID_REQUEST = { - [SECURITY]: 'sec1', - [DATA_PARTNER_ID]: 'dpid1', + const SINGLE_BID_REQUEST = { [DATA_PARTNER_PIXEL_ID]: 'pid1', - [ALG]: 'ihr', [NQ]: [SEARCH_QUERY, null], sizes: [WIDTH + 'x' + HEIGHT], bidId: '24a1c9ec270973', @@ -91,16 +84,16 @@ describe('nanointeractive adapter tests', function () { let request = nanoBidAdapter.buildRequests([getBid(true)]); expect(request.method).to.equal('POST'); expect(request.url).to.equal(ENGINE_BASE_URL); - expect(request.data).to.equal(JSON.stringify([SINGlE_BID_REQUEST])); + expect(request.data).to.equal(JSON.stringify([SINGLE_BID_REQUEST])); stub.restore(); }); it('Test interpretResponse() length', function () { - let bids = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)]); + let bids = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]}); expect(bids.length).to.equal(1); }); it('Test interpretResponse() bids', function () { - let bid = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)])[0]; + let bid = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]})[0]; expect(bid.requestId).to.equal(VALID_BID.requestId); expect(bid.cpm).to.equal(VALID_BID.cpm); expect(bid.width).to.equal(VALID_BID.width); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index eabf1f81f32..0b0e8e54ebb 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -15,7 +15,7 @@ describe('OpenxAdapter', () => { }); describe('isBidRequestValid', () => { - let bid = { + const bid = { 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -29,7 +29,7 @@ describe('OpenxAdapter', () => { 'auctionId': '1d1a030790a475', }; - let videoBid = { + const videoBid = { 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -73,7 +73,7 @@ describe('OpenxAdapter', () => { }); describe('buildRequests for banner ads', () => { - let bidRequestsWithNoMediaType = [{ + const bidRequestsWithNoMediaType = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -85,7 +85,7 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; - let bidRequests = [{ + const bidRequestsWithMediaType = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -98,21 +98,40 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': {banner: {}}, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }]; it('should send bid request to openx url via GET, even without mediaType specified', () => { const request = spec.buildRequests(bidRequestsWithNoMediaType); - expect(request[0].url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx url via GET, with mediaType specified as banner', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); expect(request[0].method).to.equal('GET'); }); - it('should send bid request to openx url via GET', () => { - const request = spec.buildRequests(bidRequests); - expect(request[0].url).to.equal('//' + bidRequests[0].params.delDomain + URLBASE); + it('should send bid request to openx url via GET, with mediaTypes specified with banner type', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('//' + bidRequestsWithNoMediaType[0].params.delDomain + URLBASE); expect(request[0].method).to.equal('GET'); }); it('should have the correct parameters', () => { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequestsWithNoMediaType); const dataParams = request[0].data; expect(dataParams.auid).to.exist; @@ -122,8 +141,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom params on bids that have customParams specified', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -141,8 +160,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom floors on bids that have customFloors specified', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -160,8 +179,8 @@ describe('OpenxAdapter', () => { }); it('should send out custom bc parameter, if override is present', () => { - let bidRequest = Object.assign({}, - bidRequests[0], + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], { params: { 'unit': '12345678', @@ -180,7 +199,25 @@ describe('OpenxAdapter', () => { }); describe('buildRequests for video', () => { - let bidRequests = [{ + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'mediaTypes': {video: {}}, + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'video': { + 'url': 'abc.com', + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + + const bidRequestsWithMediaType = [{ 'bidder': 'openx', 'mediaType': 'video', 'params': { @@ -198,14 +235,20 @@ describe('OpenxAdapter', () => { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' }]; - it('should send bid request to openx url via GET', () => { - const request = spec.buildRequests(bidRequests); - expect(request[0].url).to.equal('http://' + bidRequests[0].params.delDomain + URLBASEVIDEO); + it('should send bid request to openx url via GET, with mediaType as video', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('http://' + bidRequestsWithMediaType[0].params.delDomain + URLBASEVIDEO); + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx url via GET, with mediaTypes having video parameter', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('http://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); expect(request[0].method).to.equal('GET'); }); it('should have the correct parameters', () => { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequestsWithMediaTypes); const dataParams = request[0].data; expect(dataParams.auid).to.exist; @@ -216,7 +259,7 @@ describe('OpenxAdapter', () => { }); describe('interpretResponse for banner ads', () => { - let bids = [{ + const bids = [{ 'bidder': 'openx', 'params': { 'unit': '12345678', @@ -229,13 +272,13 @@ describe('OpenxAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }]; - let bidRequest = { + const bidRequest = { method: 'GET', url: 'url', data: {}, payload: {'bids': bids, 'startTime': new Date()} }; - let bidResponse = { + const bidResponse = { 'ads': { 'version': 1, @@ -272,7 +315,7 @@ describe('OpenxAdapter', () => { } }; it('should return correct bid response', () => { - let expectedResponse = [ + const expectedResponse = [ { 'requestId': '30b31c1838de1e', 'cpm': 1, @@ -287,12 +330,12 @@ describe('OpenxAdapter', () => { } ]; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(Object.keys(result[0])).to.eql(Object.keys(expectedResponse[0])); }); it('handles nobid responses', () => { - let bidResponse = { + const bidResponse = { 'ads': { 'version': 1, @@ -302,13 +345,13 @@ describe('OpenxAdapter', () => { } }; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(result.length).to.equal(0); }); }); describe('interpretResponse for video ads', () => { - let bids = [{ + const bids = [{ 'bidder': 'openx', 'mediaType': 'video', 'params': { @@ -325,13 +368,13 @@ describe('OpenxAdapter', () => { 'auctionId': '1d1a030790a475', 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' }]; - let bidRequest = { + const bidRequest = { method: 'GET', url: 'url', data: {}, payload: {'bid': bids[0], 'startTime': new Date()} }; - let bidResponse = { + const bidResponse = { 'pub_rev': '1', 'width': '640', 'height': '480', @@ -341,7 +384,7 @@ describe('OpenxAdapter', () => { }; it('should return correct bid response', () => { - let expectedResponse = [ + const expectedResponse = [ { 'requestId': '30b31c1838de1e', 'bidderCode': 'openx', @@ -357,13 +400,13 @@ describe('OpenxAdapter', () => { } ]; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); }); it('handles nobid responses', () => { - let bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - let result = spec.interpretResponse({body: bidResponse}, bidRequest); + const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; + const result = spec.interpretResponse({body: bidResponse}, bidRequest); expect(result.length).to.equal(0); }); }); diff --git a/test/spec/modules/optimaticBidAdapter_spec.js b/test/spec/modules/optimaticBidAdapter_spec.js index 890c0b78613..d701d981f37 100644 --- a/test/spec/modules/optimaticBidAdapter_spec.js +++ b/test/spec/modules/optimaticBidAdapter_spec.js @@ -106,7 +106,7 @@ describe('OptimaticBidAdapter', () => { expect(bidResponse.length).to.equal(0); }); - it('should return no bids if the response "adm" is missing', () => { + it('should return no bids if the response "nurl" and "adm" are missing', () => { const serverResponse = {seatbid: [{bid: [{price: 5.01}]}]}; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); expect(bidResponse.length).to.equal(0); @@ -118,7 +118,7 @@ describe('OptimaticBidAdapter', () => { expect(bidResponse.length).to.equal(0); }); - it('should return a valid bid response', () => { + it('should return a valid bid response with just "adm"', () => { const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: ''}]}], cur: 'USD'}; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); let o = { @@ -136,5 +136,43 @@ describe('OptimaticBidAdapter', () => { }; expect(bidResponse).to.deep.equal(o); }); + + it('should return a valid bid response with just "nurl"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + let o = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].id, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + + it('should return a valid bid response with "nurl" when both nurl and adm exist', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: '', nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + let o = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].id, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); }); }); diff --git a/test/spec/modules/optimeraBidAdapter_spec.js b/test/spec/modules/optimeraBidAdapter_spec.js new file mode 100644 index 00000000000..413a52d2d7f --- /dev/null +++ b/test/spec/modules/optimeraBidAdapter_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import { spec } from 'modules/optimeraBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('OptimeraAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }) + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'optimera', + 'params': { + 'clientID': '0' + }, + 'adUnitCode': 'div-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }) + + describe('buildRequests', () => { + let bid = [ + { + 'adUnitCode': 'div-0', + 'auctionId': '1ab30503e03994', + 'bidId': '313e0afede8cdb', + 'bidder': 'optimera', + 'bidderRequestId': '202be1ce3f6194', + 'params': { + 'clientID': '0' + } + } + ]; + it('buildRequests fires', () => { + let request = spec.buildRequests(bid); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + expect(request.payload).to.exist; + expect(request.data.t).to.exist; + }); + }) + + describe('interpretResponse', () => { + let serverResponse = {}; + serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"]}'); + var bidRequest = { + 'method': 'get', + 'payload': [ + { + 'bidder': 'optimera', + 'params': { + 'clientID': '0' + }, + 'adUnitCode': 'div-0', + 'bidId': '307440db8538ab' + } + ] + } + it('interpresResponse fires', () => { + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses[0].dealId[0]).to.equal('RB_K'); + expect(bidResponses[0].dealId[1]).to.equal('728x90K'); + }); + }); +}); diff --git a/test/spec/modules/peak226BidAdapter_spec.js b/test/spec/modules/peak226BidAdapter_spec.js new file mode 100644 index 00000000000..37bbc1b67bd --- /dev/null +++ b/test/spec/modules/peak226BidAdapter_spec.js @@ -0,0 +1,114 @@ +import { expect } from 'chai'; +import { spec } from 'modules/peak226BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'a.ad216.com/header_bid'; + +describe('PeakAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bid = { + params: { + uid: 123 + } + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + const bid = { + params: {} + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + xdescribe('buildRequests', () => { + const bidRequests = [ + { + params: { + uid: '1234' + } + } + ]; + + it('sends bid request to URL via GET', () => { + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.equal(`${URL}?uids=1234`); + expect(request.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', () => { + it('should handle empty response', () => { + let bids = spec.interpretResponse( + {}, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle no seatbid returned', () => { + let response = {}; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle empty seatbid returned', () => { + let response = { seatbid: [] }; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle seatbid returned bids', () => { + const bidsMap = { 1: [{ bidId: 11 }] }; + const bid = { + price: 0.2, + auid: 1, + h: 250, + w: 300, + adm: 'content' + }; + const response = { + seatbid: [ + { + seat: 'foo', + bid: [bid] + } + ] + }; + + let bids = spec.interpretResponse({ body: response }, { bidsMap }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].cpm).to.equal(bid.price); + expect(bids[0].width).to.equal(bid.w); + expect(bids[0].height).to.equal(bid.h); + expect(bids[0].ad).to.equal(bid.adm); + expect(bids[0].bidderCode).to.equal(spec.code); + }); + }); +}); diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js index d775229c9e3..7caca2e0d17 100644 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -1,14 +1,14 @@ import {expect} from 'chai'; import {spec} from 'modules/platformioBidAdapter'; import {getTopWindowLocation} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; -describe('Platformio Adapter Tests', () => { +describe('Platform.io Adapter Tests', () => { const slotConfigs = [{ placementCode: '/DfpAccount1/slot1', - sizes: [[300, 250]], bidId: 'bid12345', params: { - pubId: '28082', + pubId: '29521', siteId: '26047', placementId: '123', size: '300x250', @@ -16,15 +16,32 @@ describe('Platformio Adapter Tests', () => { } }, { placementCode: '/DfpAccount2/slot2', - sizes: [[250, 250]], bidId: 'bid23456', params: { - pubId: '28082', + pubId: '29521', siteId: '26047', - placementId: '456', - size: '250x250' + placementId: '1234', + size: '728x90', + bidFloor: '0.000001' } }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + nativeParams: { + title: { required: true, len: 200 }, + body: {}, + image: { wmin: 100 }, + sponsoredBy: { }, + icon: { } + }, + params: { + pubId: '29521', + placementId: '123', + siteId: '26047' + } + }]; + it('Verify build request', () => { const request = spec.buildRequests(slotConfigs); expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); @@ -33,7 +50,7 @@ describe('Platformio Adapter Tests', () => { // site object expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); - expect(ortbRequest.site.publisher.id).to.equal('28082'); + expect(ortbRequest.site.publisher.id).to.equal('29521'); expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); expect(ortbRequest.imp).to.have.lengthOf(2); @@ -47,10 +64,10 @@ describe('Platformio Adapter Tests', () => { expect(ortbRequest.imp[0].banner.h).to.equal(250); expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); // slot 2 - expect(ortbRequest.imp[1].tagid).to.equal('456'); + expect(ortbRequest.imp[1].tagid).to.equal('1234'); expect(ortbRequest.imp[1].banner).to.not.equal(null); - expect(ortbRequest.imp[1].banner.w).to.equal(250); - expect(ortbRequest.imp[1].banner.h).to.equal(250); + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); }); @@ -62,8 +79,7 @@ describe('Platformio Adapter Tests', () => { bid: [{ impid: ortbRequest.imp[0].id, price: 1.25, - adm: 'This is an Ad', - adid: '471810', + adm: 'This is an Ad' }] }], cur: 'USD' @@ -76,7 +92,9 @@ describe('Platformio Adapter Tests', () => { expect(bid.ad).to.equal('This is an Ad'); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('471810'); + expect(bid.adId).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); + expect(bid.netRevenue).to.equal(true); expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(360); }); @@ -87,13 +105,101 @@ describe('Platformio Adapter Tests', () => { expect(bids).to.have.lengthOf(0); }); + it('Verify Native request', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // // native impression + expect(ortbRequest.imp[0].tagid).to.equal('123'); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(5); + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[3].id).to.equal(4); + expect(nativeRequest.assets[4].id).to.equal(5); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.not.equal(null); + expect(nativeRequest.assets[0].title.len).to.equal(200); + expect(nativeRequest.assets[1].title).to.be.undefined; + expect(nativeRequest.assets[1].data).to.not.equal(null); + expect(nativeRequest.assets[1].data.type).to.equal(2); + expect(nativeRequest.assets[1].data.len).to.equal(200); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[3].img).to.not.equal(null); + expect(nativeRequest.assets[3].img.wmin).to.equal(50); + expect(nativeRequest.assets[3].img.hmin).to.equal(50); + expect(nativeRequest.assets[3].img.type).to.equal(1); + expect(nativeRequest.assets[4].img).to.not.equal(null); + expect(nativeRequest.assets[4].img.wmin).to.equal(100); + expect(nativeRequest.assets[4].img.hmin).to.equal(150); + expect(nativeRequest.assets[4].img.type).to.equal(3); + }); + + it('Verify Native response', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + const nativeResponse = { + 'native': { + assets: [ + { id: 1, title: { text: 'Ad Title' } }, + { id: 2, data: { value: 'Test description' } }, + { id: 3, data: { value: 'Brand' } }, + { id: 4, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_icon.png' } }, + { id: 5, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_image.png' } } + ], + link: { url: 'http://brand.com/' } + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'http://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) + }] + }], + cur: 'USD', + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('Brand'); + expect(nativeBid.icon).to.equal('https://s3.amazonaws.com/adx1public/creatives_icon.png'); + expect(nativeBid.image).to.equal('https://s3.amazonaws.com/adx1public/creatives_image.png'); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('http://rtb.adx1.com/log'); + }); + it('Verifies bidder code', () => { expect(spec.code).to.equal('platformio'); }); + it('Verifies supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('native'); + }); + it('Verifies if bid request valid', () => { expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 608ac102ace..58a71158865 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -243,6 +243,41 @@ describe('S2S Adapter', () => { expect(requestBid.ad_units[0].bids[0].params.placementId).to.exist.and.to.be.a('number'); expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string'); }); + + it('adds digitrust id is present and user is not optout', () => { + let digiTrustObj = { + success: true, + identity: { + privacy: { + optout: false + }, + id: 'testId', + keyv: 'testKeyV' + } + }; + + window.DigiTrust = { + getUser: () => digiTrustObj + }; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.digiTrust).to.deep.equal({ + id: digiTrustObj.identity.id, + keyv: digiTrustObj.identity.keyv, + pref: 0 + }); + + digiTrustObj.identity.privacy.optout = true; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.digiTrust).to.not.exist; + + delete window.DigiTrust; + }); }); describe('response handler', () => { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js new file mode 100644 index 00000000000..cbf17f9478a --- /dev/null +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -0,0 +1,180 @@ +import {expect} from 'chai'; +import {spec} from 'modules/pubmaticBidAdapter'; +import * as utils from 'src/utils'; +const constants = require('src/constants.json'); + +describe('PubMatic adapter', () => { + let bidRequests; + let bidResponses; + + beforeEach(() => { + bidRequests = [ + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'image3.pubmatic.com Layer based creative', + 'h': 250, + 'w': 300 + }] + }] + } + }; + }); + + describe('implementation', () => { + describe('Bid validations', () => { + it('valid bid case', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: publisherId not passed', () => { + let validBid = { + bidder: 'pubmatic', + params: { + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: publisherId is not string', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: 301, + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: adSlot not passed', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: adSlot is not string', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: 15671365 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + + describe('Request formation', () => { + it('Endpoint checking', () => { + let request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('//hbopenbid.pubmatic.com/translator?source=prebid-client'); + expect(request.method).to.equal('POST'); + }); + + it('Request params check', () => { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(bidRequests[0].params.profId); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(bidRequests[0].params.verId); // OpenWrap: Wrapper Profile Version ID + + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + }); + + it('invalid adslot', () => { + bidRequests[0].params.adSlot = '/15671365/DMDemo'; + let request = spec.buildRequests(bidRequests); + expect(request).to.equal(undefined); + }); + }); + + describe('Response checking', () => { + it('should check for valid response values', () => { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + expect(response[0].referrer).to.include(utils.getTopWindowUrl()); + expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + }); + }); + }); +}); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index cb99f1b5d98..933b65c4574 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -228,8 +228,8 @@ describe('PulsePoint Adapter Tests', () => { }); it('Verifies supported media types', () => { - expect(spec.supportedMediaTypes).to.have.lengthOf(1); - expect(spec.supportedMediaTypes[0]).to.equal('native'); + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[1]).to.equal('native'); }); it('Verifies if bid request valid', () => { diff --git a/test/spec/modules/rockyouBidAdapter_spec.js b/test/spec/modules/rockyouBidAdapter_spec.js new file mode 100644 index 00000000000..8b38a333bca --- /dev/null +++ b/test/spec/modules/rockyouBidAdapter_spec.js @@ -0,0 +1,455 @@ +import { expect } from 'chai'; +import { spec, internals } from 'modules/rockyouBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('RockYouAdapter', () => { + const adapter = newBidder(spec); + + describe('bid validator', () => { + it('rejects a bid that is missing the placementId', () => { + let testBid = {}; + expect(spec.isBidRequestValid(testBid)).to.be.false; + }); + + it('accepts a bid with all the expected parameters', () => { + let testBid = { + params: { + placementId: 'f39ba81609' + } + }; + + expect(spec.isBidRequestValid(testBid)).to.be.true; + }); + }); + + describe('request builder', () => { + // Taken from the docs, so used as much as is valid + const sampleBidRequest = { + 'bidder': 'tests', + 'bidId': '51ef8751f9aead', + 'params': { + 'cId': '59ac1da80784890004047d89', + 'placementId': 'ZZZPLACEMENTZZZ' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }; + + it('successfully generates a URL', () => { + const placementId = 'ZZZPLACEMENTZZZ'; + + let bidRequests = [ + { + 'params': { + 'placementId': placementId + } + } + ]; + + let result = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + + expect(result.url).to.not.be.undefined; + expect(result.url).to.not.be.null; + + expect(result.url).to.include('/servlet/rotator/' + placementId + '/0/vo?z=') + }); + + it('uses the bidId id as the openRtb request ID', () => { + const bidId = '51ef8751f9aead'; + + let bidRequests = [ + sampleBidRequest + ]; + + let result = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + expect(payload.id).to.equal(bidId); + }); + + it('generates the device payload as expected', () => { + let bidRequests = [ + sampleBidRequest + ]; + + let result = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + let userData = payload.user; + + expect(userData).to.not.be.null; + }); + + it('generates multiple imp bodies', () => { + let bidRequests = [ + sampleBidRequest, + sampleBidRequest + ]; + + let result = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + expect(payload.imp).to.not.be.null; + expect(payload.imp.length).to.equal(2); + }); + + it('generates a banner request as expected', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { banner: {} }; + + let result = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('generates a banner request using a singular adSize instead of an array', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = [320, 50]; + localBidRequest.mediaTypes = { banner: {} }; + + let result = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('fails gracefully on an invalid size', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = ['x', 'w']; + localBidRequest.mediaTypes = { banner: {} }; + + let result = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(null); + expect(bannerData.h).to.equal(null); + }); + + it('generates a video request as expected', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { video: {} }; + + let result = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.video).to.not.be.null; + + let videoData = firstImp.video; + + expect(videoData.w).to.equal(320); + expect(videoData.h).to.equal(50); + }); + + it('propagates the mediaTypes object in the built request', () => { + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { video: {} }; + + let result = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + + let mediaTypes = result.mediaTypes; + + expect(mediaTypes).to.not.be.null; + expect(mediaTypes).to.not.be.undefined; + expect(mediaTypes.video).to.not.be.null; + expect(mediaTypes.video).to.not.be.undefined; + }); + }); + + describe('response interpreter', () => { + it('returns an empty array when no bids present', () => { + // an empty JSON body indicates no ad was found + + let result = spec.interpretResponse({ body: '' }, {}) + + expect(result).to.eql([]); + }); + + it('gracefully fails when a non-JSON body is present', () => { + let result = spec.interpretResponse({ body: 'THIS IS NOT ' }, {}) + + expect(result).to.eql([]); + }); + + it('returns a valid bid response on sucessful banner request', () => { + let incomingRequestId = 'XXtestingXX'; + let responsePrice = 3.14 + + let responseCreative = '\n
\n
\n
'; + + let responseCreativeId = '274'; + let responseCurrency = 'USD'; + + let responseWidth = 300; + let responseHeight = 250; + let responseTtl = 213; + + let sampleResponse = { + id: '66043f5ca44ecd8f8769093b1615b2d9', + seatbid: [ + { + bid: [ + { + id: 'c21bab0e-7668-4d8f-908a-63e094c09197', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + adm: responseCreative, + adomain: [ + 'www.rockyouteststudios.com' + ], + cid: '274', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + } + ], + seat: '201', + group: 0 + } + ], + bidid: 'c21bab0e-7668-4d8f-908a-63e094c09197', + cur: responseCurrency + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { banner: {} }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse( + { + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(responseCreative); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + }); + + it('returns an valid bid response on sucessful video request', () => { + let incomingRequestId = 'XXtesting-275XX'; + let responsePrice = 6 + + let responseCreative = '\n\n \n \n Mediatastic\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n'; + + let responseCreativeId = '1556'; + let responseCurrency = 'USD'; + + let responseWidth = 284; + let responseHeight = 285; + let responseTtl = 286; + + let sampleResponse = { + id: '1234567890', + seatbid: [ + { + bid: [ + { + id: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + adm: responseCreative, + cid: '270', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + } + ], + seat: '201', + group: 0 + } + ], + bidid: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + cur: 'USD' + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + video: { + } + }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse( + { + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(null); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + expect(processedBid.vastXml).to.equal(responseCreative); + }); + + it('generates event callbacks as expected', () => { + let tally = {}; + let renderer = { + handleVideoEvent: (eventObject) => { + let eventName = eventObject.eventName; + if (tally[eventName]) { + tally[eventName] = tally[eventName] + 1; + } else { + tally[eventName] = 1; + } + } + }; + + let callbacks = internals.playerCallbacks(renderer); + + let validCallbacks = ['LOAD', 'IMPRESSION', 'COMPLETE', 'ERROR']; + + validCallbacks.forEach(event => { + callbacks('n/a', event); + }); + + let callbackKeys = Object.keys(tally); + expect(callbackKeys.length).to.equal(3); + expect(tally['loaded']).to.equal(1); + expect(tally['impression']).to.equal(1); + expect(tally['ended']).to.equal(2); + }); + + it('generates a renderer that will hide on complete', () => { + let elementName = 'test_element_id'; + let selector = `#${elementName}`; + + let mockElement = { + style: { + display: 'some' + } + }; + + document.querySelector = (name) => { + if (name === selector) { + return mockElement; + } else { + return null; + } + }; + + let renderer = internals.generateRenderer({}, elementName); + + renderer.handlers['ended'](); + + expect(mockElement.style.display).to.equal('none'); + }) + }); +}); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b02f01ecd93..2ff8d5fe0e3 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -760,6 +760,8 @@ describe('the rubicon adapter', () => { 'https://fastlane-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' ); expect(bids[0].impression_id).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].videoCacheKey).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); }); }); }); diff --git a/test/spec/modules/serverbidBidAdapter_spec.js b/test/spec/modules/serverbidBidAdapter_spec.js index fb31f925c6e..d3dc64ae6df 100644 --- a/test/spec/modules/serverbidBidAdapter_spec.js +++ b/test/spec/modules/serverbidBidAdapter_spec.js @@ -245,10 +245,10 @@ describe('Serverbid BidAdapter', () => { expect(opts).to.be.empty; }); - it('should always return empty array', () => { + it('should return a sync url if iframe syncs are enabled', () => { let opts = spec.getUserSyncs(syncOptions); - expect(opts).to.be.empty; + expect(opts.length).to.equal(1); }); }); }); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js new file mode 100644 index 00000000000..5cb4a1277fb --- /dev/null +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -0,0 +1,157 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/smartadserverBidAdapter'; +import { + getTopWindowLocation +} from 'src/utils'; +import { + newBidder +} from 'src/adapters/bidderFactory'; +import { + config +} from 'src/config'; +import * as utils from 'src/utils'; + +describe('Smart ad server bid adapter tests', () => { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smartadserver', + params: { + domain: 'http://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420 + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smartadserver', + params: { + domain: 'http://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90' + }, + requestId: 'efgh5678' + }]; + + var BID_RESPONSE = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >' + } + }; + + it('Verify build request', () => { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request).to.have.property('url').and.to.equal('http://prg.smartadserver.com/prebid/v1'); + expect(request).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request.data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('90'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); + expect(requestContent).to.have.property('pageDomain').and.to.equal(utils.getTopWindowUrl()); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + }); + + it('Verify parse response', () => { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(12); + expect(bid.adUrl).to.equal('http://awesome.fake.url'); + expect(bid.ad).to.equal('< --- awesome script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('zioeufg'); + expect(bid.currency).to.equal('GBP'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + expect(bid.referrer).to.equal(utils.getTopWindowUrl()); + }); + + it('Verifies bidder code', () => { + expect(spec.code).to.equal('smartadserver'); + }); + + it('Verifies bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('smart'); + }); + + it('Verifies if bid request valid', () => { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pageid: 123 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + siteid: 123 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + formatid: 123, + pageid: 234 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + domain: 'www.test.com', + pageid: 234 + } + })).to.equal(false); + }); + + it('Verifies sync options', () => { + expect(spec.getUserSyncs).to.be.undefined; + }); +}); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js new file mode 100644 index 00000000000..6a44c817d0b --- /dev/null +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -0,0 +1,243 @@ +import { expect } from 'chai' +import { spec } from 'modules/sonobiBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +describe('SonobiBidAdapter', () => { + const adapter = newBidder(spec) + + describe('.code', () => { + it('should return a bidder code of sonobi', () => { + expect(spec.code).to.equal('sonobi') + }) + }) + + describe('inherited functions', () => { + it('should exist and be a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('.isBidRequestValid', () => { + let bid = { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_MR', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.ad_unit and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + 'sizes': [[300, 250], [300, 600]], + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.ad_unit and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when no params are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should return false when bid.params.placement_id and bid.params.ad_unit are not found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.params = { + 'placement_id': 0, + 'ad_unit': 0, + 'sizes': [[300, 250], [300, 600]], + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('.buildRequests', () => { + let bidRequest = [{ + 'bidder': 'sonobi', + 'params': { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25', + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + }, + { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]], + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'bidId': '30b31c1838de1e', + }]; + + let keyMakerData = { + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25', + '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600', + }; + + it('should return a properly formatted request', () => { + const bidRequests = spec.buildRequests(bidRequest) + const bidRequestsPageViewID = spec.buildRequests(bidRequest) + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') + expect(bidRequests.method).to.equal('GET') + expect(bidRequests.data.key_maker).to.deep.equal(JSON.stringify(keyMakerData)) + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(bidRequests.data.hfa).to.not.exist + }) + + it('should return a properly formatted request with hfa', () => { + bidRequest[0].params.hfa = 'hfakey' + bidRequest[1].params.hfa = 'hfakey' + const bidRequests = spec.buildRequests(bidRequest) + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') + expect(bidRequests.method).to.equal('GET') + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.hfa).to.equal('hfakey') + }) + }) + + describe('.interpretResponse', () => { + let bidResponse = { + 'body': { + 'slots': { + '/7780971/sparks_prebid_LB|30b31c1838de1d': { + 'sbi_size': '300x600', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b039', + 'sbi_mouse': 1.07, + }, + '30b31c1838de1f': { + 'sbi_size': '300x250', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b038', + 'sbi_mouse': 1.25, + 'sbi_dozer': 'dozerkey', + }, + '30b31c1838de1e': {}, + }, + 'sbi_dc': 'mco-1-', + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://example.com/pixel.png', + 'type': 'image' + }], + 'sbi_suid': 'af99f47a-e7b1-4791-ab32-34952d87c5a0', + } + }; + + let prebidResponse = [{ + 'requestId': '30b31c1838de1d', + 'cpm': 1.07, + 'width': 300, + 'height': 600, + 'ad': '', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b039', + 'netRevenue': true, + 'currency': 'USD' + }, { + 'requestId': '30b31c1838de1f', + 'cpm': 1.25, + 'width': 300, + 'height': 250, + 'ad': '', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b038', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'dozerkey' + }]; + + it('should map bidResponse to prebidResponse', () => { + const response = spec.interpretResponse(bidResponse); + expect(response).to.deep.equal(prebidResponse); + }) + }) + + describe('.getUserSyncs', () => { + let bidResponse = [{ + 'body': { + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://pixel-test', + 'type': 'image' + }] + } + }]; + + it('should return one sync pixel', () => { + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.deep.equal([{ + type: 'image', + url: 'https://pixel-test' + }]); + }) + + it('should return an empty array when sync is enabled but no sync pixel returned', () => { + const pixel = Object.assign({}, bidResponse); + delete pixel[0].body.sbi_px; + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.have.length(0); + }) + + it('should return an empty array', () => { + expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse)).to.have.length(0); + }) + }) +}) diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js new file mode 100644 index 00000000000..152c7c39b1e --- /dev/null +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -0,0 +1,108 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ucfunnelBidAdapter'; + +const URL = '//hb.aralego.com/header'; +const BIDDER_CODE = 'ucfunnel'; +const validBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 123456789 + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; + +const validBidRes = { + ad_id: 'ad-83444226E44368D1E32E49EEBE6D29', + adm: '
', + cpm: 0.01, + height: 250, + width: 300 +}; + +const bidResponse = validBidRes; + +const bidResArray = [ + validBidRes, + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + }, + { + ad: '
Hello
', + bidRequestId: '', + cpm: 0, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; + +describe('ucfunnel Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(false); + }); + }); + describe('build request', () => { + it('Verify bid request', () => { + const request = spec.buildRequests(bidReq); + expect(request[0].method).to.equal('GET'); + expect(request[0].url).to.equal(URL); + expect(request[0].data).to.match(new RegExp(`${bidReq[0].params.adid}`)); + }); + }); + + describe('interpretResponse', () => { + it('should build bid array', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + const bid = result[0]; + + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(0.01); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + }); +}); diff --git a/test/spec/modules/vertamediaBidAdapter_spec.js b/test/spec/modules/vertamediaBidAdapter_spec.js index 15466a94aca..32353b0097e 100644 --- a/test/spec/modules/vertamediaBidAdapter_spec.js +++ b/test/spec/modules/vertamediaBidAdapter_spec.js @@ -2,9 +2,25 @@ import {expect} from 'chai'; import {spec} from 'modules/vertamediaBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; -const ENDPOINT = '//rtb.vertamedia.com/hb/'; -const REQUEST = { +const ENDPOINT = '//hb2.vertamedia.com/auction/'; + +const DISPLAY_REQUEST = { + 'bidder': 'vertamedia', + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [300, 250] +}; + +const VIDEO_REQUEST = { 'bidder': 'vertamedia', + 'mediaTypes': { + 'video': {} + }, 'params': { 'aid': 12345 }, @@ -12,10 +28,10 @@ const REQUEST = { 'auctionId': '2e41f65424c87c', 'adUnitCode': 'adunit-code', 'bidId': '84ab500420319d', - 'sizes': [640, 480] + 'sizes': [[480, 360], [640, 480]] }; -const serverResponse = { +const SERVER_VIDEO_RESPONSE = { 'source': {'aid': 12345, 'pubId': 54321}, 'bids': [{ 'vastUrl': 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', @@ -30,6 +46,55 @@ const serverResponse = { } ] }; +const SERVER_DISPLAY_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }] +}; + +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{mediaTypes: {video: {}}, bidId: '2e41f65424c87c'}] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}] +}; + +const videoEqResponse = [{ + vastUrl: 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 3600, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'display', + netRevenue: true, + currency: 'USD', + ad: '', + height: 250, + width: 300, + ttl: 3600, + cpm: 0.9 +}]; describe('vertamediaBidAdapter', () => { const adapter = newBidder(spec); @@ -42,37 +107,56 @@ describe('vertamediaBidAdapter', () => { describe('isBidRequestValid', () => { it('should return true when required params found', () => { - expect(spec.isBidRequestValid(REQUEST)).to.equal(true); + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(12345); }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, REQUEST); + let bid = Object.assign({}, VIDEO_REQUEST); delete bid.params; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid)).to.equal(undefined); }); }); describe('buildRequests', () => { - let bidRequests = [REQUEST]; + let videoBidRequests = [VIDEO_REQUEST]; + let dispalyBidRequests = [DISPLAY_REQUEST]; - const request = spec.buildRequests(bidRequests, {}); + const displayRequest = spec.buildRequests(dispalyBidRequests, {}); + const videoRequest = spec.buildRequests(videoBidRequests, {}); it('sends bid request to ENDPOINT via GET', () => { - expect(request[0].method).to.equal('GET'); + expect(videoRequest[0].method).to.equal('GET'); + expect(displayRequest[0].method).to.equal('GET'); }); + it('sends bid request to correct ENDPOINT', () => { - expect(request[0].url).to.equal(ENDPOINT); + expect(videoRequest[0].url).to.equal(ENDPOINT); + expect(displayRequest[0].url).to.equal(ENDPOINT); + }); + + it('sends correct video bid parameters', () => { + const bid = Object.assign({}, videoRequest[0].data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'video', + aid: 12345, + sizes: '480x360,640x480' + }; + + expect(bid).to.deep.equal(eq); }); - it('sends correct bid parameters', () => { - const bid = Object.assign({}, request[0].data); + it('sends correct display bid parameters', () => { + const bid = Object.assign({}, displayRequest[0].data); delete bid.domain; const eq = { callbackId: '84ab500420319d', + ad_type: 'display', aid: 12345, - w: 640, - h: 480 + sizes: '300x250' }; expect(bid).to.deep.equal(eq); @@ -80,34 +164,55 @@ describe('vertamediaBidAdapter', () => { }); describe('interpretResponse', () => { - let bidderRequest = { - bidderCode: 'bidderCode', - bids: [{mediaTypes: {video: {}}}] - }; + let serverResponse; + let bidderRequest; + let eqResponse; + + afterEach(() => { + serverResponse = null; + bidderRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + bidderRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + bidderRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); - it('should get correct bid response', () => { + function bidServerResponseCheck() { const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); - const eq = [{ - vastUrl: 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', - requestId: '2e41f65424c87c', - creativeId: 342516, - mediaType: 'video', - netRevenue: true, - currency: 'USD', - height: 480, - width: 640, - ttl: 3600, - cpm: 0.9 - }]; - - expect(result).to.deep.equal(eq); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = {bids: []}; + const noBidResult = spec.interpretResponse({body: noBidServerResponse}, {bidderRequest}); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + bidderRequest = videoBidderRequest; + + nobidServerResponseCheck(); }); - it('handles nobid responses', () => { - const nobidServerResponse = {bids: []}; - const nobidResult = spec.interpretResponse({body: nobidServerResponse}, {bidderRequest}); + it('handles display nobid responses', () => { + bidderRequest = displayBidderRequest; - expect(nobidResult.length).to.equal(0); + nobidServerResponseCheck(); }); }); }); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js new file mode 100644 index 00000000000..e8b0fbcc4b2 --- /dev/null +++ b/test/spec/modules/viBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from 'modules/viBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = `//pb.vi-serve.com/prebid/bid`; + +describe('viBidAdapter', function() { + newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [320, 480] + ], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pubId not passed', () => { + bid.params.pubId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [{ + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [320, 480] + ], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('buildRequests can handle size in 1-dim array', () => { + let bidRequests = [{ + 'bidder': 'vi', + 'params': { + 'pubId': 'sb_test', + 'lang': 'en-US', + 'cat': 'IAB1', + 'bidFloor': 0.05 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [320, 480], + 'bidId': '29b891ad542377', + 'bidderRequestId': '1dc9a08206a57b', + 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('interpretResponse', () => { + let response = { + body: [{ + 'id': '29b891ad542377', + 'price': 0.1, + 'width': 320, + 'height': 480, + 'ad': '', + 'creativeId': 'dZsPGv' + }] + }; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '29b891ad542377', + 'cpm': 0.1, + 'width': 320, + 'height': 480, + 'creativeId': 'dZsPGv', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(``), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', () => { + let response = { + body: [] + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js new file mode 100644 index 00000000000..a4f8d0dfdb2 --- /dev/null +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -0,0 +1,183 @@ +import {expect} from 'chai'; +import {spec as adapter, URL} from 'modules/vidazooBidAdapter'; +import * as utils from 'src/utils'; +const BID = { + 'bidId': '2d52001cabd527', + 'params': { + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a' +}; + +const SERVER_RESPONSE = { + body: { + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +const SYNC_OPTIONS = { + 'pixelEnabled': true +}; + +describe('VidazooBidAdapter', () => { + describe('validtae spec', () => { + it('exists and is a function', () => { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', () => { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + }); + + describe('validate bid requests', () => { + it('should require cId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', () => { + let sandbox; + before(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getTopWindowUrl', () => 'http://www.greatsite.com'); + sandbox.stub(Date, 'now', () => 1000); + }); + + it('should build request for each size', () => { + const requests = adapter.buildRequests([BID]); + expect(requests).to.have.length(2); + expect(requests[0]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '250', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3' + } + }); + expect(requests[1]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '600', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3' + } + }); + }); + + after(() => { + sandbox.restore(); + }); + }); + + describe('interpret response', () => { + it('should return empty array when there is no response', () => { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', () => { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', () => { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted responses', () => { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '' + }); + }); + + it('should take default TTL', () => { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); +}); diff --git a/test/spec/modules/xendizBidAdapter_spec.js b/test/spec/modules/xendizBidAdapter_spec.js new file mode 100644 index 00000000000..1b0ef9c8d4c --- /dev/null +++ b/test/spec/modules/xendizBidAdapter_spec.js @@ -0,0 +1,119 @@ +import { expect } from 'chai'; +import { spec } from 'modules/xendizBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const VALID_ENDPOINT = '//prebid.xendiz.com/request'; +const bidRequest = { + bidder: 'xendiz', + adUnitCode: 'test-div', + sizes: [[300, 250], [300, 600]], + params: { + pid: '550e8400-e29b-41d4-a716-446655440000' + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', +}; + +const bidResponse = { + body: { + id: '1d1a030790a475', + bids: [{ + id: '30b31c1838de1e', + price: 3, + cur: 'USD', + h: 250, + w: 300, + crid: 'test', + dealid: '1', + exp: 900, + adm: 'tag' + }] + } +}; + +const noBidResponse = { body: { id: '1d1a030790a475', bids: [] } }; + +describe('xendizBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return false', () => { + let bid = Object.assign({}, bidRequest); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid request body', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.id).to.exist; + expect(payload.items).to.exist; + expect(payload.device).to.exist; + }); + + it('should attach valid device info', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.device).to.deep.equal([ + navigator.language, + window.screen.width, + window.screen.height + ]); + }); + + it('should transform sizes', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const item = payload.items[0]; + expect(item[item.length - 1]).to.deep.equal(['300x250', '300x600']); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const result = spec.interpretResponse(bidResponse); + const validResponse = [{ + requestId: '30b31c1838de1e', + cpm: 3, + width: 300, + height: 250, + creativeId: 'test', + netRevenue: true, + dealId: '1', + currency: 'USD', + ttl: 900, + ad: 'tag' + }]; + + expect(result).to.deep.equal(validResponse); + }); + + it('handles nobid responses', () => { + let result = spec.interpretResponse(noBidResponse); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js new file mode 100644 index 00000000000..2a19763b10a --- /dev/null +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -0,0 +1,111 @@ +import { expect } from 'chai' +import { spec } from 'modules/yieldlabBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +const REQUEST = { + 'bidder': 'yieldlab', + 'params': { + 'placementId': '1111', + 'accountId': '2222', + 'adSize': '800x250' + }, + 'bidderRequestId': '143346cf0f1731', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '2d925f27f5079f', + 'sizes': [1, 1] +} + +const RESPONSE = { + advertiser: 'yieldlab', + curl: 'https://www.yieldlab.de', + format: 0, + id: 1111, + price: 1 +} + +describe('yieldlabBidAdapter', () => { + const adapter = newBidder(spec) + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const request = { + 'params': { + 'placementId': '1111', + 'accountId': '2222', + 'adSize': '800x250' + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bidRequests = [REQUEST] + const request = spec.buildRequests(bidRequests) + + it('sends bid request to ENDPOINT via GET', () => { + expect(request.method).to.equal('GET') + }) + + it('returns a list of valid requests', () => { + expect(request.validBidRequests).to.eql([REQUEST]) + }) + }) + + describe('interpretResponse', () => { + const validRequests = { + validBidRequests: [REQUEST] + } + + it('handles nobid responses', () => { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }) + + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, validRequests) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].width).to.equal(800) + expect(result[0].height).to.equal(250) + expect(result[0].creativeId).to.equal('1111') + expect(result[0].dealId).to.equal(undefined) + expect(result[0].currency).to.equal('EUR') + expect(result[0].netRevenue).to.equal(true) + expect(result[0].ttl).to.equal(600) + expect(result[0].referrer).to.equal('') + expect(result[0].ad).to.include('
', + creativeId: '9874652394875' + }], + header: 'header?' + }; + }) + + it('should correctly reorder the server response', () => { + const newResponse = spec.interpretResponse(serverResponse); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + requestId: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + creativeId: '9874652394875', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '
' + }); + }); + + it('should not add responses if the cpm is 0 or null', () => { + serverResponse.body[0].cpm = 0; + let response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]); + + serverResponse.body[0].cpm = null; + response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]) + }); + }); + + describe('getUserSync', () => { + const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; + let options = { + iframeEnabled: true, + pixelEnabled: true + }; + + it('should return a tracker with type and url as parameters', () => { + if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) { + expect(spec.getUserSync(options)).to.deep.equal([{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]); + + options.iframeEnabled = false; + expect(spec.getUserSync(options)).to.deep.equal([]); + } else { + // not ios, so tracker will fail + expect(spec.getUserSync(options)).to.deep.equal([]); + } + }); + }); +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index c1f7fe82252..9605babf3d9 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -897,54 +897,22 @@ describe('Unit: Prebid Module', function () { }); }); - describe.skip('#video', () => { + describe('multiformat requests', () => { let spyCallBids; let createAuctionStub; let adUnits; - before(() => { + beforeEach(() => { adUnits = [{ code: 'adUnit-code', - mediaType: 'video', + mediaTypes: { + banner: {}, + native: {}, + }, sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexus', params: {placementId: 'id'}}, - {bidder: 'sampleBidder', params: {placementId: 'id'}} - ] - }]; - adUnitCodes = ['adUnit-code']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adaptermanager, 'callBids'); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); - }); - - after(() => { - auctionModule.newAuction.restore(); - adaptermanager.callBids.restore(); - }); - - it('should not callBids if a video adUnit has non-video bidders', () => { - const videoAdaptersBackup = adaptermanager.videoAdapters; - adaptermanager.videoAdapters = ['appnexus']; - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.notCalled(adaptermanager.callBids); - adaptermanager.videoAdapters = videoAdaptersBackup; - }); - }); - - describe('#video', () => { - let spyCallBids; - let createAuctionStub; - let adUnits; - - before(() => { - adUnits = [{ - code: 'adUnit-code', - mediaType: 'video', - sizes: [[300, 250], [300, 600]], - bids: [ - {bidder: 'appnexus', params: {placementId: 'id'}} + {bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}} ] }]; adUnitCodes = ['adUnit-code']; @@ -954,53 +922,32 @@ describe('Unit: Prebid Module', function () { createAuctionStub.returns(auction); }) - after(() => { + afterEach(() => { auctionModule.newAuction.restore(); adaptermanager.callBids.restore(); }); - it('should callBids if a video adUnit has all video bidders', () => { - const videoAdaptersBackup = adaptermanager.videoAdapters; - adaptermanager.videoAdapters = ['appnexus']; + it('bidders that support one of the declared formats are allowed to participate', () => { $$PREBID_GLOBAL$$.requestBids({adUnits}); sinon.assert.calledOnce(adaptermanager.callBids); - adaptermanager.videoAdapters = videoAdaptersBackup; - }); - }); - describe('#native', () => { - let spyCallBids; - let createAuctionStub; - let adUnits; + const spyArgs = adaptermanager.callBids.getCall(0); + const biddersCalled = spyArgs.args[0][0].bids; - before(() => { - adUnits = [{ - code: 'adUnit-code', - mediaType: 'native', - sizes: [[300, 250], [300, 600]], - bids: [ - {bidder: 'appnexus', params: {placementId: 'id'}}, - {bidder: 'sampleBidder', params: {placementId: 'id'}} - ] - }]; - adUnitCodes = ['adUnit-code']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adaptermanager, 'callBids'); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); + // appnexus and sampleBidder both support banner + expect(biddersCalled.length).to.equal(2); }); - after(() => { - auctionModule.newAuction.restore(); - adaptermanager.callBids.restore(); - }); + it('bidders that do not support one of the declared formats are dropped', () => { + delete adUnits[0].mediaTypes.banner; - it('should only request native bidders on native adunits', () => { - // appnexus is a native bidder, appnexus is not $$PREBID_GLOBAL$$.requestBids({adUnits}); sinon.assert.calledOnce(adaptermanager.callBids); + const spyArgs = adaptermanager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; + + // only appnexus supports native expect(biddersCalled.length).to.equal(1); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 9f7c57ccb81..522d28b79ad 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -525,94 +525,89 @@ describe('Utils', function () { }); }); - /** - * tests fail in IE10 because __lookupSetter__ and __lookupGetter__ are - * not supported. See #1656. commenting out until they can be fixed. - * - * describe('cookie support', function () { - * // store original cookie getter and setter so we can reset later - * var origCookieSetter = document.__lookupSetter__('cookie'); - * var origCookieGetter = document.__lookupGetter__('cookie'); - * - * // store original cookieEnabled getter and setter so we can reset later - * var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); - * var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); - * - * // Replace the document cookie set function with the output of a custom function for testing - * let setCookie = (v) => v; - * - * beforeEach(() => { - * // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values - * Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { - * return { - * get: function _get() { - * return _value; - * }, - * set: function _set(v) { - * _value = v; - * }, - * configurable: true - * }; - * })(window.navigator.cookieEnabled)); - * - * // Reset the setCookie cookie function before each test - * setCookie = (v) => v; - * // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled - * Object.defineProperty(window.document, 'cookie', (function (_value) { - * return { - * get: function _get() { - * return _value; - * }, - * set: function _set(v) { - * _value = setCookie(v); - * }, - * configurable: true - * }; - * })(window.navigator.cookieEnabled)); - * }); - * - * afterEach(() => { - * // redefine window.navigator.cookieEnabled to original getter and setter - * Object.defineProperty(window.navigator, 'cookieEnabled', { - * get: origCookieEnabledGetter, - * set: origCookieEnabledSetter, - * configurable: true - * }); - * // redefine document.cookie to original getter and setter - * Object.defineProperty(document, 'cookie', { - * get: origCookieGetter, - * set: origCookieSetter, - * configurable: true - * }); - * }); - * - * it('should be detected', function() { - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); - * }); - * - * it('should be not available', function() { - * setCookie = () => ''; - * window.navigator.cookieEnabled = false; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); - * }); - * - * it('should be available', function() { - * window.navigator.cookieEnabled = false; - * window.document.cookie = 'key=value'; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); - * window.navigator.cookieEnabled = false; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); - * setCookie = () => ''; - * window.navigator.cookieEnabled = true; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); - * // Reset the setCookie - * setCookie = (v) => v; - * }); - * }); - **/ + describe('cookie support', function () { + // store original cookie getter and setter so we can reset later + var origCookieSetter = document.__lookupSetter__('cookie'); + var origCookieGetter = document.__lookupGetter__('cookie'); + + // store original cookieEnabled getter and setter so we can reset later + var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); + var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); + + // Replace the document cookie set function with the output of a custom function for testing + let setCookie = (v) => v; + + beforeEach(() => { + // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values + Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { + return { + get: function _get() { + return _value; + }, + set: function _set(v) { + _value = v; + }, + configurable: true + }; + })(window.navigator.cookieEnabled)); + + // Reset the setCookie cookie function before each test + setCookie = (v) => v; + // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled + Object.defineProperty(window.document, 'cookie', (function (_value) { + return { + get: function _get() { + return _value; + }, + set: function _set(v) { + _value = setCookie(v); + }, + configurable: true + }; + })(window.navigator.cookieEnabled)); + }); + + afterEach(() => { + // redefine window.navigator.cookieEnabled to original getter and setter + Object.defineProperty(window.navigator, 'cookieEnabled', { + get: origCookieEnabledGetter, + set: origCookieEnabledSetter, + configurable: true + }); + // redefine document.cookie to original getter and setter + Object.defineProperty(document, 'cookie', { + get: origCookieGetter, + set: origCookieSetter, + configurable: true + }); + }); + + it('should be detected', function() { + assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); + }); + + it('should be not available', function() { + setCookie = () => ''; + window.navigator.cookieEnabled = false; + window.document.cookie = ''; + assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); + }); + + it('should be available', function() { + window.navigator.cookieEnabled = false; + window.document.cookie = 'key=value'; + assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); + window.navigator.cookieEnabled = false; + window.document.cookie = ''; + assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); + setCookie = () => ''; + window.navigator.cookieEnabled = true; + window.document.cookie = ''; + assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); + // Reset the setCookie + setCookie = (v) => v; + }); + }); describe('delayExecution', function () { it('should execute the core function after the correct number of calls', function () {