diff --git a/.circleci/config.yml b/.circleci/config.yml index fbf7e77e10f..16bdd5b317e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,4 +47,4 @@ jobs: # run tests! - run: name: BrowserStack testing - command: gulp test --browserstack + command: gulp test --browserstack --nolintfix diff --git a/gulpfile.js b/gulpfile.js index 8775b651b9f..d3b29aa66a7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -83,7 +83,7 @@ function lint(done) { return file.eslint != null && file.eslint.fixed; } return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], {base: './'}) - .pipe(eslint({fix: true})) + .pipe(gulpif(argv.nolintfix, eslint(), eslint({fix: true}))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 1e00485a6d9..77b6acbf0e0 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,7 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; import {BANNER, NATIVE} from '../src/mediaTypes'; -import { config } from '../src/config'; +import {config} from '../src/config'; const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { @@ -156,6 +156,9 @@ function createNativeAd(body) { case 6: native.cta = assets[i].data.value; break; + case 502: + native.privacyLink = encodeURIComponent(assets[i].data.value); + break; } } native.clickUrl = body.native_ad.link.url; diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md index 7d8281be9b2..7dfc301e657 100644 --- a/modules/adgenerationBidAdapter.md +++ b/modules/adgenerationBidAdapter.md @@ -52,7 +52,10 @@ var adUnits = [ }, icon: { required: true - } + }, + privacyLink: { + required: true + }, }, }, bids: [ diff --git a/modules/adpod.js b/modules/adpod.js index 87e5869e17b..fe49c10e193 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -132,6 +132,7 @@ function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { } bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] = pcd; bid.adserverTargeting[TARGETING_KEY_CACHE_ID] = initialCacheKey; + bid.videoCacheKey = initialCacheKey; bid.customCacheKey = `${pcd}_${initialCacheKey}`; } @@ -412,3 +413,18 @@ export function callPrebidCacheAfterAuction(bids, callback) { } }) } + +/** + * Compare function to be used in sorting long-form bids. This will compare bids on price per second. + * @param {Object} bid + * @param {Object} bid + */ +export function sortByPricePerSecond(a, b) { + if (a.cpm / a.video.durationBucket < b.cpm / b.video.durationBucket) { + return 1; + } + if (a.cpm / a.video.durationBucket > b.cpm / b.video.durationBucket) { + return -1; + } + return 0; +} diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js old mode 100644 new mode 100755 index 926be211649..e98de8dd77e --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -7,7 +7,7 @@ import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'avng'; +const BIDDER_CODE = 'advangelists'; export const VIDEO_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; export const BANNER_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; diff --git a/modules/advangelistsBidAdapter.md b/modules/advangelistsBidAdapter.md old mode 100644 new mode 100755 index 14e2befd48f..1765241eaf3 --- a/modules/advangelistsBidAdapter.md +++ b/modules/advangelistsBidAdapter.md @@ -24,7 +24,7 @@ var displayAdUnit = [ [320, 50] ], bids: [{ - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '0cf8d6d643e13d86a5b6374148a4afac', placement: 1234 @@ -47,7 +47,7 @@ var videoAdUnit = { }, bids: [ { - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '8537f00948fc37cc03c5f0f88e198a76', placement: 1234, diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 73b70fe8e72..23808fa3be7 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -10,6 +10,7 @@ import includes from 'core-js/library/fn/array/includes' * updated to latest prebid repo on 2017.10.20 * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters + * updated to fix native support for image width/height and icon 2019.03.17 */ const BIDDER_CODE = 'adxcg' @@ -210,8 +211,10 @@ export const spec = { let nativeResponse = serverResponseOneItem.nativeResponse bid['native'] = { - clickUrl: encodeURIComponent(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers, + clickTrackers: nativeResponse.clktrackers, + javascriptTrackers: nativeResponse.jstrackers } nativeResponse.assets.forEach(asset => { @@ -220,7 +223,19 @@ export const spec = { } if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url + let nativeImage = {} + nativeImage.url = asset.img.url + nativeImage.height = asset.img.h + nativeImage.width = asset.img.w + bid['native'].image = nativeImage + } + + if (asset.icon && asset.icon.url) { + let nativeIcon = {} + nativeIcon.url = asset.icon.url + nativeIcon.height = asset.icon.h + nativeIcon.width = asset.icon.w + bid['native'].icon = nativeIcon } if (asset.data && asset.data.label === 'DESC' && asset.data.value) { diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 4cbe9a840ea..5b826d1e725 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,7 @@ import { Renderer } from '../src/Renderer'; import * as utils from '../src/utils'; import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'aja'; const URL = '//ad.as.amanad.adtdp.com/v2/prebid'; @@ -14,7 +14,7 @@ const AD_TYPE = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function(bid) { return !!(bid.params.asi); @@ -86,6 +86,41 @@ export const spec = { } catch (error) { utils.logError('Error appending tracking pixel', error); } + } else if (AD_TYPE.NATIVE === ad.ad_type) { + const nativeAds = ad.native.template_and_ads.ads; + + nativeAds.forEach(nativeAd => { + const assets = nativeAd.assets; + + Object.assign(bid, { + mediaType: NATIVE + }); + + bid.native = { + title: assets.title, + body: assets.description, + cta: assets.cta_text, + sponsoredBy: assets.sponsor, + clickUrl: assets.lp_link, + impressionTrackers: nativeAd.imps, + }; + + if (assets.img_main !== undefined) { + bid.native.image = { + url: assets.img_main, + width: parseInt(assets.img_main_width, 10), + height: parseInt(assets.img_main_height, 10) + }; + } + + if (assets.img_icon !== undefined) { + bid.native.icon = { + url: assets.img_icon, + width: parseInt(assets.img_icon_width, 10), + height: parseInt(assets.img_icon_height, 10) + }; + } + }); } return [bid]; diff --git a/modules/ajaBidAdapter.md b/modules/ajaBidAdapter.md index 6e513b94ff0..4f5aa7074cb 100644 --- a/modules/ajaBidAdapter.md +++ b/modules/ajaBidAdapter.md @@ -11,11 +11,11 @@ Connects to Aja exchange for bids. Aja bid adapter supports Banner and Outstream Video. # Test Parameters -``` +```js var adUnits = [ // Banner adUnit { - code: 'banner-div', + code: 'prebid_banner', mediaTypes: { banner: { sizes: [ @@ -26,13 +26,13 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'szs4htFiR' + asi: 'tk82gbLmg' } }] }, // Video outstream adUnit { - code: 'video-outstream', + code: 'prebid_video', mediaTypes: { video: { context: 'outstream', @@ -42,7 +42,45 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'Kp2O2tFig' + asi: '1-KwEG_iR' + } + }] + }, + // Native adUnit + { + code: 'prebid_native', + mediaTypes: { + native: { + image: { + required: true, + sendId: false + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false, + sendId: true + }, + clickUrl: { + required: false, + sendId: true + }, + body: { + required: false, + sendId: true + }, + icon: { + required: false, + sendId: false + } + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: 'qxueUGliR' } }] } diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js new file mode 100644 index 00000000000..626aa99f5de --- /dev/null +++ b/modules/brightcomBidAdapter.js @@ -0,0 +1,246 @@ +import * as utils from '../src/utils'; +import * as url from '../src/url'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'brightcom'; +const URL = 'https://brightcombid.marphezis.com/hb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const brightcomImps = []; + const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); + utils._each(bidReqs, function (bid) { + bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); + bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + if (bidFloor) { + imp.bidfloor = bidFloor; + } + brightcomImps.push(imp); + }); + const brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site: { + domain: url.parse(referrer).host, + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + return { + method: 'POST', + url: URL, + data: JSON.stringify(brightcomBidReq), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + utils.logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.publisherId === 'undefined') { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + if (!serverResponse.body || typeof serverResponse.body != 'object') { + utils.logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return []; + } + const { body: {id, seatbid} } = serverResponse; + try { + const brightcomBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(brightcomBid => { + brightcomBidResponses.push({ + requestId: brightcomBid.impid, + cpm: parseFloat(brightcomBid.price), + width: parseInt(brightcomBid.w), + height: parseInt(brightcomBid.h), + creativeId: brightcomBid.crid || brightcomBid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(brightcomBid), + ttl: 60 + }); + }); + } + return brightcomBidResponses; + } catch (e) { + utils.logError(e, {id, seatbid}); + } +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return utils.getWindowTop().document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md new file mode 100644 index 00000000000..badc6ea94a4 --- /dev/null +++ b/modules/brightcomBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Brightcom Bid Adapter +Module Type: Bidder Adapter +Maintainer: vladislavy@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + sizes: [[728, 90]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + sizes: [[300, 250]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 9374be62452..d462acaee59 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -28,10 +28,7 @@ export const spec = { * @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. - + buildRequests: function(validBidRequests, bidderRequest) { let ret = { method: 'POST', url: '', @@ -50,14 +47,21 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - user: {}, url: utils.getTopWindowUrl(), referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true + source: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] }, validBidRequests[0].params); + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + } + validBidRequests.map(bid => { const placement = Object.assign({ divName: bid.bidId, @@ -123,12 +127,16 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', url: '//sync.serverbid.com/ss/' + siteId + '.html' }]; + } + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + return serverResponses[0].body.pixels; } else { utils.logWarn(bidder + ': Please enable iframe based user syncing.'); } @@ -192,12 +200,7 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let oad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); - let cb = Math.round(new Date().getTime()); - let ad = '' + oad; - ad += ''; - ad += ''; - ad += '' + let ad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); return ad; } diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js new file mode 100644 index 00000000000..2b5cd0421c2 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.js @@ -0,0 +1,406 @@ +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; +import { parse as parseURL } from '../src/url'; + +const CONSTANTS = require('../src/constants.json'); + +const ANALYTICS_TYPE = 'endpoint'; +const FINTEZA_HOST = 'https://content.mql5.com/tr'; +const BID_REQUEST_TRACK = 'Bid Request %BIDDER%'; +const BID_RESPONSE_TRACK = 'Bid Response %BIDDER%'; +const BID_TIMEOUT_TRACK = 'Bid Timeout %BIDDER%'; +const BID_WON_TRACK = 'Bid Won %BIDDER%'; + +const FIRST_VISIT_DATE = '_fz_fvdt'; +const SESSION_ID = '_fz_ssn'; +const SESSION_DURATION = 30 * 60 * 1000; +const SESSION_RAND_PART = 9; +const TRACK_TIME_KEY = '_fz_tr'; + +function getPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + + if (document.referrer) { + pageInfo.referrerDomain = parseURL(document.referrer).hostname; + } + + return pageInfo; +} + +function initFirstVisit() { + let now; + let visitDate; + let cookies; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + visitDate = cookies[ FIRST_VISIT_DATE ]; + + if (!visitDate) { + now = new Date(); + + visitDate = parseInt(now.getTime() / 1000, 10); + + now.setFullYear(now.getFullYear() + 20); + + try { + document.cookie = FIRST_VISIT_DATE + '=' + visitDate + '; path=/; expires=' + now.toUTCString(); + } catch (e) {} + } + + return visitDate; +} + +function trim(string) { + if (string.trim) { + return string.trim(); + } + return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); +} + +function parseCookies(cookie) { + let values = {}; + let arr, item; + let param, value; + let i, j; + + if (!cookie) { + return {}; + } + + arr = cookie.split(';'); + + for (i = 0, j = arr.length; i < j; i++) { + item = arr[ i ]; + if (!item) { + continue; + } + + param = item.split('='); + if (param.length <= 1) { + continue; + } + + value = decodeURIComponent(param[0]); + value = trim(value); + + values[value] = decodeURIComponent(param[1]); + } + + return values; +} + +function getRandAsStr(digits) { + let str = ''; + let rand = 0; + let i; + + digits = digits || 4; + + for (i = 0; i < digits; i++) { + rand = (Math.random() * 10) >>> 0; + str += '' + rand; + } + + return str; +} + +function getSessionBegin(session) { + if (!session || (typeof session !== 'string')) { + return 0; + } + + const len = session.length; + if (len && len <= SESSION_RAND_PART) { + return 0; + } + + const timestamp = session.substring(0, len - SESSION_RAND_PART); + + return parseInt(timestamp, 10); +} + +function initSession() { + const now = new Date(); + const expires = new Date(now.getTime() + SESSION_DURATION); + const timestamp = Math.floor(now.getTime() / 1000); + let begin = 0; + let cookies; + let sessionId; + let sessionDuration; + let isNew = false; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + sessionId = cookies[ SESSION_ID ]; + + if (!sessionId || + !checkSessionByExpires() || + !checkSessionByReferer() || + !checkSessionByDay()) { + sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); + begin = timestamp; + + isNew = true; + } else { + begin = getSessionBegin(sessionId); + } + + if (begin > 0) { + sessionDuration = Math.floor(timestamp - begin); + } else { + sessionDuration = -1; + } + + try { + document.cookie = SESSION_ID + '=' + sessionId + '; path=/; expires=' + expires.toUTCString(); + } catch (e) {} + + return { + isNew: isNew, + id: sessionId, + duration: sessionDuration + }; +} + +function checkSessionByExpires() { + const timestamp = getTrackRequestLastTime(); + const now = new Date().getTime(); + + if (now > timestamp + SESSION_DURATION) { + return false; + } + return true; +} + +function checkSessionByReferer() { + const referrer = fntzAnalyticsAdapter.context.pageInfo.referrerDomain; + const domain = fntzAnalyticsAdapter.context.pageInfo.domain; + + return referrer === '' || domain === referrer; +} + +function checkSessionByDay() { + let last = getTrackRequestLastTime(); + if (last) { + last = new Date(last); + const now = new Date(); + + return last.getUTCDate() === now.getUTCDate() && + last.getUTCMonth() === now.getUTCMonth() && + last.getUTCFullYear() === now.getUTCFullYear(); + } + + return false; +} + +function saveTrackRequestTime() { + const now = new Date().getTime(); + const expires = new Date(now + SESSION_DURATION); + + try { + if (window.localStorage) { + window.localStorage.setItem(TRACK_TIME_KEY, now.toString()); + } else { + document.cookie = TRACK_TIME_KEY + '=' + now + '; path=/; expires=' + expires.toUTCString(); + } + } catch (a) {} +} + +function getTrackRequestLastTime() { + let cookie; + + try { + if (window.localStorage) { + return parseInt( + window.localStorage.getItem(TRACK_TIME_KEY) || 0, + 10, + ); + } + + cookie = parseCookies(document.cookie); + cookie = cookie[ TRACK_TIME_KEY ]; + if (cookie) { + return parseInt(cookie, 10); + } + } catch (e) {} + + return 0; +} + +function getAntiCacheParam() { + const date = new Date(); + const rand = (Math.random() * 99999 + 1) >>> 0; + + return ([ date.getTime(), rand ].join('')); +} + +function replaceBidder(str, bidder) { + let _str = str; + _str = _str.replace(/\%bidder\%/, bidder.toLowerCase()); + _str = _str.replace(/\%BIDDER\%/, bidder.toUpperCase()); + _str = _str.replace(/\%Bidder\%/, bidder.charAt(0).toUpperCase() + bidder.slice(1).toLowerCase()); + + return _str; +} + +function prepareBidRequestedParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidRequestTrack, args.bidderCode)), + ref: encodeURIComponent(window.location.href), + }]; +} + +function prepareBidResponseParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponseTrack, args.bidderCode)), + c1_value: args.timeToRespond, + c1_unit: 'ms', + c2_value: args.cpm, + c2_unit: 'usd', + }]; +} + +function prepareBidWonParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidWonTrack, args.bidderCode)), + c1_value: args.cpm, + c1_unit: 'usd', + }]; +} + +function prepareBidTimeoutParams(args) { + return args.map(function(bid) { + return { + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidTimeoutTrack, bid.bidder)), + c1_value: bid.timeout, + c1_unit: 'ms', + }; + }) +} + +function prepareTrackData(evtype, args) { + let prepareParams = null; + + switch (evtype) { + case CONSTANTS.EVENTS.BID_REQUESTED: + prepareParams = prepareBidRequestedParams; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + prepareParams = prepareBidResponseParams; + break; + case CONSTANTS.EVENTS.BID_WON: + prepareParams = prepareBidWonParams; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + prepareParams = prepareBidTimeoutParams; + break; + } + + if (!prepareParams) { return null; } + + const data = prepareParams(args); + + if (!data) { return null; } + + const session = initSession(); + + return data.map(d => { + const trackData = Object.assign(d, { + id: fntzAnalyticsAdapter.context.id, + ref: encodeURIComponent(window.location.href), + title: encodeURIComponent(document.title), + scr_res: fntzAnalyticsAdapter.context.screenResolution, + fv_date: fntzAnalyticsAdapter.context.firstVisit, + ac: getAntiCacheParam(), + }) + + if (session.id) { + trackData.ssn = session.id; + } + if (session.isNew) { + session.isNew = false; + trackData.ssn_start = 1; + } + trackData.ssn_dr = session.duration; + + return trackData; + }); +} + +function sendTrackRequest(trackData) { + try { + ajax( + fntzAnalyticsAdapter.context.host, + null, // Callback + trackData, + { + method: 'GET', + contentType: 'application/x-www-form-urlencoded', + // preflight: true, + }, + ); + saveTrackRequestTime(); + } catch (err) { + utils.logError('Error on send data: ', err); + } +} + +const fntzAnalyticsAdapter = Object.assign( + adapter({ + FINTEZA_HOST, + ANALYTICS_TYPE + }), + { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + const trackData = prepareTrackData(eventType, args); + if (!trackData) { return; } + + trackData.forEach(sendTrackRequest); + } + } + } +); + +fntzAnalyticsAdapter.originEnableAnalytics = fntzAnalyticsAdapter.enableAnalytics; + +fntzAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.id) { + utils.logError('Client ID (id) option is not defined. Analytics won\'t work'); + return; + } + + fntzAnalyticsAdapter.context = { + host: config.options.host || FINTEZA_HOST, + id: config.options.id, + bidRequestTrack: config.options.bidRequestTrack || BID_REQUEST_TRACK, + bidResponseTrack: config.options.bidResponseTrack || BID_RESPONSE_TRACK, + bidTimeoutTrack: config.options.bidTimeoutTrack || BID_TIMEOUT_TRACK, + bidWonTrack: config.options.bidWonTrack || BID_WON_TRACK, + firstVisit: initFirstVisit(), + screenResolution: `${window.screen.width}x${window.screen.height}`, + pageInfo: getPageInfo(), + }; + + fntzAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: fntzAnalyticsAdapter, + code: 'finteza' +}); + +export default fntzAnalyticsAdapter; diff --git a/modules/fintezaAnalyticsAdapter.md b/modules/fintezaAnalyticsAdapter.md new file mode 100644 index 00000000000..22525f55366 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Finteza Analytics Adapter +Module Type: Analytics Adapter +Maintainer: renat@finteza.com +``` + +# Description + +The Finteza adapter for integration with Prebid is an analytics tool for publishers who use the Header Bidding technology. The adapter tracks auction opening, offer sending to advertisers, receipt of bids by the publisher and auction winner selection. All tracks are sent to Finteza and enable visual advertiser quality evaluation: how many offers partners accept, what prices they provide, how fast they respond and how often their bids win. + +For more information, visit the [official Finteza website](https://www.finteza.com/). + +# Test Parameters + +``` +{ + provider: 'finteza', + options: { + id: 'xxxxx', // Website ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTrack: 'Bid Response %BIDDER%', + bidTimeoutTrack: 'Bid Timeout %BIDDER%', + bidWonTrack: 'Bid Won %BIDDER%' + } +} +``` diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js index 01ed145b00b..c81f3356d71 100644 --- a/modules/freeWheelAdserverVideo.js +++ b/modules/freeWheelAdserverVideo.js @@ -4,10 +4,10 @@ import { registerVideoSupport } from '../src/adServerManager'; import { auctionManager } from '../src/auctionManager'; -import { groupBy, deepAccess, logError } from '../src/utils'; +import { groupBy, deepAccess, logError, compareOn } from '../src/utils'; import { config } from '../src/config'; import { ADPOD } from '../src/mediaTypes'; -import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction } from './adpod'; +import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction, sortByPricePerSecond } from './adpod'; import { getHook } from '../src/hook'; export function notifyTranslationModule(fn) { @@ -37,7 +37,7 @@ export function getTargeting({codes, callback} = {}) { let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - bids.sort(compareOn('cpm')); + bids.sort(sortByPricePerSecond); let targeting = {}; if (deferCachingEnabled === false) { @@ -119,18 +119,6 @@ function getAdPodAdUnits(codes) { .filter((adUnit) => (codes.length > 0) ? codes.indexOf(adUnit.code) != -1 : true); } -function compareOn(property) { - return function compare(a, b) { - if (a[property] < b[property]) { - return 1; - } - if (a[property] > b[property]) { - return -1; - } - return 0; - } -} - /** * This function removes bids of same freewheel category. It will be used when competitive exclusion is enabled. * @param {Array[Object]} bidsReceived diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index e465e44b25b..4671f054042 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -116,6 +116,11 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) { } bidResponse.ts = adUnit.ts; + bidResponse.meta = {}; + if (adUnit.brand_id) { + bidResponse.meta.brandId = adUnit.brand_id; + } + bidResponses.push(bidResponse); registerBeacon(BANNER, adUnit, startTime); diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md index 84df1d1ad07..909c0a46cd6 100644 --- a/modules/optimeraBidAdapter.md +++ b/modules/optimeraBidAdapter.md @@ -34,3 +34,23 @@ Module that adds ad placement visibility scores for DFP. }] }]; ``` + +# AppNexus Issue +There is an issue where the plugin sometimes doesn't return impressions with AppNexus. + +There is an open issue here: [#3597](https://github.com/prebid/Prebid.js/issues/3597) + +## Configuration Workaround + +Optimera's configuration requires the use of size 0,0 which, in some instances, causes the AppNexus ad server to respond to ad requests but not fill impressions. AppNexus and vendors using the AppNexus ad server should monitor 3rd party numbers to ensure there is no decline in fill rate. + +Configuration Example: + +``` +code: ‘leaderboard', +mediaTypes: { + banner: { + sizes: [[970, 250],[970, 90],[970, 66],[728, 90],[320, 50],[320, 100],[300, 250],[0, 0]], + } +} +``` diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index de365fe87de..e316f3ef212 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -18,6 +18,7 @@ export const spec = { return !!(bid.sizes && bid.bidId && bid.params && (bid.params.accountId && (typeof bid.params.accountId === 'string')) && (bid.params.placementId && (typeof bid.params.placementId === 'string')) && + ((typeof bid.params.bidfloor === 'undefined') || (typeof bid.params.bidfloor === 'number')) && ((typeof bid.params.keyValues === 'undefined') || (typeof bid.params.keyValues === 'object'))); }, diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index 78015d69594..57ac414c7b6 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,5 +1,5 @@ -import {BANNER} from '../src/mediaTypes'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import {registerBidder} from 'src/adapters/bidderFactory'; export const spec = { code: 'otm', @@ -9,10 +9,11 @@ export const spec = { }, buildRequests: function (bidRequests) { const requests = bidRequests.map(function (bid) { + const size = getMaxPrioritySize(bid.sizes); const params = { tz: getTz(), - w: bid.sizes[0][0], - h: bid.sizes[0][1], + w: size[0], + h: size[1], s: bid.params.tid, bidid: bid.bidId, transactionid: bid.transactionId, @@ -57,4 +58,38 @@ function getTz() { return new Date().getTimezoneOffset(); } +function getMaxPrioritySize(sizes) { + var maxPrioritySize = null; + + const sizesByPriority = [ + [300, 250], + [240, 400], + [728, 90], + [300, 600], + [970, 250], + [300, 50], + [320, 100] + ]; + + const sizeToString = (size) => { + return size[0] + 'x' + size[1]; + }; + + const sizesAsString = sizes.map(sizeToString); + + sizesByPriority.forEach(size => { + if (!maxPrioritySize) { + if (sizesAsString.indexOf(sizeToString(size)) !== -1) { + maxPrioritySize = size; + } + } + }); + + if (maxPrioritySize) { + return maxPrioritySize; + } else { + return sizes[0]; + } +} + registerBidder(spec); diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 7a7033a79ac..406bffdf4df 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -7,7 +7,7 @@ const BIDDER_CODE = 'ozone'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; -const OZONEVERSION = '1.4.4'; +const OZONEVERSION = '1.4.7'; export const spec = { code: BIDDER_CODE, @@ -18,72 +18,78 @@ export const spec = { */ isBidRequestValid(bid) { if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.placementId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); return false; } if (bid.params.hasOwnProperty('customData')) { if (typeof bid.params.customData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); return false; } } if (bid.params.hasOwnProperty('customParams')) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); return false; } if (bid.params.hasOwnProperty('ozoneData')) { if (typeof bid.params.ozoneData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); return false; } } if (bid.params.hasOwnProperty('lotameData')) { if (typeof bid.params.lotameData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); return false; } } return true; }, buildRequests(validBidRequests, bidderRequest) { - utils.logInfo('ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); - utils.logInfo('buildRequests setting auctionId', bidderRequest.auctionId); + utils.logInfo('OZONE: ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + utils.logInfo('OZONE: buildRequests setting auctionId', bidderRequest.auctionId); let singleRequest = config.getConfig('ozone.singleRequest'); + singleRequest = singleRequest !== false; // undefined & true will be true - utils.logInfo('config ozone.singleRequest : ', singleRequest); + utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); let htmlParams = validBidRequests[0].params; // the html page config params will be included in each element let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params // ozoneRequest['id'] = utils.generateUUID(); delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] if (bidderRequest.gdprConsent) { + utils.logInfo('OZONE: ADDING GDPR info'); ozoneRequest.regs = {}; ozoneRequest.regs.ext = {}; ozoneRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; if (ozoneRequest.regs.ext.gdpr) { - ozoneRequest.regs.ext.consent = bidderRequest.gdprConsent.consentString; + ozoneRequest.user = {}; + ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; } + } else { + utils.logInfo('OZONE: WILL NOT ADD GDPR info'); } + ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; obj.id = ozoneBidRequest.bidId; // this causes a failure if we change it to something else @@ -95,25 +101,25 @@ export const spec = { /* NOTE - if there is sizes element in the config root then there will be a mediaTypes.banner element automatically generated for us, so this code is deprecated */ if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - utils.logInfo('no mediaTypes detected - will use the sizes array in the config root'); + utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - utils.logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + utils.logInfo('OZONE: no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - utils.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } // Video integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { obj.video = ozoneBidRequest.mediaTypes[VIDEO]; - utils.logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); + utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); } // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - utils.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } } // build the banner request using banner sizes we found in either possible location: @@ -127,15 +133,10 @@ export const spec = { }) }; } - if (ozoneBidRequest.params.hasOwnProperty('placementId')) { - obj.placementId = (ozoneBidRequest.params.placementId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('publisherId')) { - obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('siteId')) { - obj.siteId = (ozoneBidRequest.params.siteId).toString(); - } + // these 3 MUST exist - we check them in the validation method + obj.placementId = (ozoneBidRequest.params.placementId).toString(); + obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); + obj.siteId = (ozoneBidRequest.params.siteId).toString(); // build the imp['ext'] object obj.ext = {'prebid': {'storedrequest': {'id': (ozoneBidRequest.params.placementId).toString()}}, 'ozone': {}}; obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' @@ -161,7 +162,7 @@ export const spec = { // utils.logInfo('_ozoneInternal is', _ozoneInternal); // return the single request object OR the array: if (singleRequest) { - utils.logInfo('buildRequests starting to generate response for a single request'); + utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; @@ -172,21 +173,21 @@ export const spec = { data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - utils.logInfo('buildRequests ozoneRequest for single = ', ozoneRequest); - utils.logInfo('buildRequests going to return for single: ', ret); + utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); + utils.logInfo('OZONE: buildRequests going to return for single: ', ret); return ret; } // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { - utils.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); imp.ext.ozone.pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.source = {'tid': imp.ext.ozone.transactionId}; - utils.logInfo('buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); + utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', url: OZONEURI, @@ -194,7 +195,7 @@ export const spec = { bidderRequest: bidderRequest }; }); - utils.logInfo('buildRequests going to return for non-single: ', arrRet); + utils.logInfo('OZONE: buildRequests going to return for non-single: ', arrRet); return arrRet; }, /** @@ -205,7 +206,7 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { - utils.logInfo('ozone v' + OZONEVERSION + ' interpretResponse', serverResponse, request); + utils.logInfo('OZONE: version' + OZONEVERSION + ' interpretResponse', serverResponse, request); serverResponse = serverResponse.body || {}; if (serverResponse.seatbid) { if (utils.isArray(serverResponse.seatbid)) { @@ -219,19 +220,19 @@ export const spec = { let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid, serverResponse.seatbid); if (winningBid == null) { - utils.logInfo('FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); + utils.logInfo('OZONE: FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); continue; } const {defaultWidth, defaultHeight} = defaultSize(arrRequestBids[i]); winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight); - utils.logInfo('Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); + utils.logInfo('OZONE: Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); let adserverTargeting = {}; let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid); // add all the winning & non-winning bids for this bidId: Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) { - utils.logInfo('inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); + utils.logInfo('OZONE: inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); adserverTargeting['oz_' + bidderName] = bidderName; adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price); adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); @@ -246,14 +247,14 @@ export const spec = { adserverTargeting['oz_response_id'] = String(serverResponse.id); winningBid.adserverTargeting = adserverTargeting; - utils.logInfo('winner is', winningBid); + utils.logInfo('OZONE: winner is', winningBid); arrWinners.push(winningBid); - utils.logInfo('arrWinners is', arrWinners); + utils.logInfo('OZONE: arrWinners is', arrWinners); } let winnersClean = arrWinners.filter(w => { return (w.bidId); // will be cast to boolean }); - utils.logInfo('going to return winnersClean:', winnersClean); + utils.logInfo('OZONE: going to return winnersClean:', winnersClean); return winnersClean; } else { return []; @@ -326,7 +327,7 @@ export function ozoneGetWinnerForRequestBid(requestBid, serverResponseSeatBid) { * @returns {} = {ozone:{obj}, appnexus:{obj}, ... } */ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { - utils.logInfo('ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { let theseBids = serverResponseSeatBid[j].bid; @@ -338,7 +339,7 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { } } } - utils.logInfo('ozoneGetAllBidsForBidId - going to return: ', objBids); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - going to return: ', objBids); return objBids; } @@ -379,4 +380,4 @@ export function getTestQuerystringValue() { } registerBidder(spec); -utils.logInfo('ozoneBidAdapter ended'); +utils.logInfo('OZONE: ozoneBidAdapter ended'); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b8da30021cd..d80d6c5c810 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes'; import {config} from '../src/config'; const BIDDER_CODE = 'pubmatic'; +const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = '//hbopenbid.pubmatic.com/translator?source=prebid-client'; const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; const DEFAULT_CURRENCY = 'USD'; @@ -175,7 +176,7 @@ function _getDomainFromURL(url) { function _parseSlotParam(paramName, paramValue) { if (!utils.isStr(paramValue)) { - paramValue && utils.logWarn('PubMatic: Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + paramValue && utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); return UNDEFINED; } @@ -221,7 +222,7 @@ function _parseAdSlot(bid) { splits = slot.split('@'); if (splits.length != 2) { if (!(sizesArrayExists)) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } } @@ -229,7 +230,7 @@ function _parseAdSlot(bid) { if (splits.length > 1) { // i.e size is specified in adslot, so consider that and ignore sizes array splits = splits[1].split('x'); if (splits.length != 2) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } bid.params.width = parseInt(splits[0]); @@ -273,7 +274,7 @@ function _handleCustomParams(params, 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); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); } } } @@ -311,25 +312,25 @@ function _checkParamDataType(key, value, datatype) { switch (datatype) { case DATA_TYPES.BOOLEAN: if (!utils.isBoolean(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.NUMBER: if (!utils.isNumber(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.STRING: if (!utils.isStr(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.ARRAY: if (!utils.isArray(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; @@ -356,7 +357,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(BIDDER_CODE + ' Error: Title Length is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.IMAGE: @@ -376,7 +377,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.ICON: @@ -392,7 +393,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); }; break; case NATIVE_ASSET_KEY.SPONSOREDBY: @@ -650,7 +651,7 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } if (isInvalidNativeRequest && impObj.hasOwnProperty('native')) { - utils.logWarn(BIDDER_CODE + ': Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); + utils.logWarn(LOG_WARN_PREFIX + 'Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); return; } return impObj; @@ -717,7 +718,7 @@ function _parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm.replace(/\\/g, '')); } catch (ex) { - utils.logWarn(BIDDER_CODE + ' Error: Cannot parse native reponse for ad response: ' + newBid.adm); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -784,17 +785,17 @@ export const spec = { isBidRequestValid: bid => { if (bid && bid.params) { if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(BIDDER_CODE + ' Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } if (!utils.isStr(bid.params.adSlot)) { - utils.logWarn(BIDDER_CODE + ': adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation if (bid.params.hasOwnProperty('video')) { if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { - utils.logWarn(BIDDER_CODE + ': For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } } @@ -826,12 +827,12 @@ export const spec = { _parseAdSlot(bid); if (bid.params.hasOwnProperty('video')) { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } else { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } @@ -841,7 +842,7 @@ export const spec = { if (bidCurrency === '') { bidCurrency = bid.params.currency || undefined; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - utils.logWarn(BIDDER_CODE + ': Currency specifier ignored. Only one currency permitted.'); + utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); } bid.params.currency = bidCurrency; // check if dctr is added to more than 1 adunit @@ -910,13 +911,13 @@ export const spec = { key_val: dctr.trim() } } else { - utils.logWarn(BIDDER_CODE + ': Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); } if (dctrArr.length > 1) { - utils.logWarn(BIDDER_CODE + ': dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); } } else { - utils.logWarn(BIDDER_CODE + ': dctr value not found in 1st adunit, ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits'); } } @@ -976,6 +977,17 @@ export const spec = { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.clickUrl = bid.adomain[0]; + } + bidResponses.push(newBid); }); }); @@ -1004,7 +1016,7 @@ export const spec = { url: syncurl }]; } else { - utils.logWarn('PubMatic: Please enable iframe based user sync.'); + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); } }, diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js new file mode 100644 index 00000000000..561ce58e016 --- /dev/null +++ b/modules/smartrtbBidAdapter.js @@ -0,0 +1,111 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'smartrtb'; + +function getDomain () { + if (!utils.inIframe()) { + return window.location.hostname + } + let origins = window.document.location.ancestorOrigins + if (origins && origins.length > 0) { + return origins[origins.length - 1] + } +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['smrtb'], + isBidRequestValid: function(bid) { + return (bid.params.pubId !== null && + bid.params.medId !== null && + bid.params.zoneId !== null); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let stack = (bidderRequest.refererInfo && + bidderRequest.refererInfo.stack ? bidderRequest.refererInfo + : []) + + const payload = { + start_time: utils.timestamp(), + tmax: 120, + language: window.navigator.userLanguage || window.navigator.language, + site: { + domain: getDomain(), + iframe: !bidderRequest.refererInfo.reachedTop, + url: stack && stack.length > 0 ? [stack.length - 1] : null, + https: (window.location.protocol === 'https:'), + referrer: bidderRequest.refererInfo.referer + }, + imps: [] + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + + for (let x = 0; x < validBidRequests.length; x++) { + let req = validBidRequests[x] + + payload.imps.push({ + pub_id: req.params.pubId, + med_id: req.params.medId, + zone_id: req.params.zoneId, + bid_id: req.bidId, + imp_id: req.transactionId, + sizes: req.sizes, + force_bid: req.params.forceBid + }); + } + + return { + method: 'POST', + url: '//pubs.smrtb.com/json/publisher/prebid', + data: JSON.stringify(payload) + }; + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + let res = serverResponse.body; + if (!res.bids || !res.bids.length) { + return [] + } + + for (let x = 0; x < serverResponse.body.bids.length; x++) { + let bid = serverResponse.body.bids[x] + + bidResponses.push({ + requestId: bid.bid_id, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + ad: bid.html, + ttl: 120, + creativeId: bid.crid, + netRevenue: true, + currency: 'USD' + }) + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//ads.smrtb.com/sync' + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: '//ads.smrtb.com/sync' + }); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/smartrtbBidAdapter.md b/modules/smartrtbBidAdapter.md new file mode 100644 index 00000000000..f043e277d0d --- /dev/null +++ b/modules/smartrtbBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Smart RTB (smrtb.com) Bidder Adapter +Module Type: Bidder Adapter +Maintainer: evanm@smrtb.com +``` + +# Description + +Prebid adapter for Smart RTB. Requires approval and account setup. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smartrtb", + params: { + pubId: 123, + medId: "m_00a95d003340dbb2fcb8ee668a84fa", + zoneId: "z_261b6c7e7d4d4985393b293cc903d1", + force_bid: true + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js new file mode 100644 index 00000000000..bf9a75b6650 --- /dev/null +++ b/modules/smilewantedBidAdapter.js @@ -0,0 +1,113 @@ +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +export const spec = { + code: 'smilewanted', + aliases: ['smile', 'sw'], + + /** + * 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 && bid.params.zoneId); + }, + + /** + * 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: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + zoneId: bid.params.zoneId, + currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', + bidfloor: bid.params.bidfloor || 0.0, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.pageDomain = bidderRequest.refererInfo.referer || ''; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + var payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: 'https://prebid.smilewanted.com', + 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, + adUrl: response.adUrl, + ad: response.ad + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing smilewanted response', error); + } + return bidResponses; + }, + + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {Syncs[]} An array of syncs that should be executed. + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } + } + return syncs; + } +} + +registerBidder(spec); diff --git a/modules/smilewantedBidAdapter.md b/modules/smilewantedBidAdapter.md new file mode 100644 index 00000000000..ddc25fe7456 --- /dev/null +++ b/modules/smilewantedBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: SmileWanted Bidder Adapter +Module Type: Bidder Adapter +Maintainer: maxime@smilewanted.com +``` + +# Description + +To use us as a bidder you must have an account and an active "zoneId" on our SmileWanted platform. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smilewanted", + params: { + zoneId: 1 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js new file mode 100644 index 00000000000..ab110cb4d01 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.js @@ -0,0 +1,277 @@ +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import {ajaxBuilder} from '../src/ajax'; + +const utils = require('../src/utils'); +let ajax = ajaxBuilder(0); + +const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; +const analyticsType = 'endpoint'; +const QUEUE_TIMEOUT_DEFAULT = 200; +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BIDDER_DONE, + BID_WON, + BID_RESPONSE, + BID_TIMEOUT + } +} = CONSTANTS; + +let initOptions = {}; +let auctionCache = {}; +let auctionTtl = 60 * 60 * 1000; + +function deleteOldAuctions() { + for (let auctionId in auctionCache) { + let auction = auctionCache[auctionId]; + if (Date.now() - auction.start > auctionTtl) { + delete auctionCache[auctionId]; + } + } +} + +function buildAuctionEntity(args) { + return { + 'id': args.auctionId, + 'start': args.timestamp, + 'timeout': args.timeout, + 'adUnits': {}, + 'stats': {}, + 'queue': [], + 'qTimeout': false + }; +} +function buildAdUnit(data) { + return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`; +} +function getLatency(data) { + if (!data.responseTimestamp) { + return -1; + } else { + return data.responseTimestamp - data.requestTimestamp; + } +} +function getBid(data) { + if (data.cpm) { + return Math.round(data.cpm * 100); + } else { + return 0; + } +} +function buildItem(data, response, phase = 1) { + let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]}; + return { + 'bidid': data.bidId || data.requestId, + 'p': phase, + 'buyerid': data.bidder.toLowerCase(), + 'bid': getBid(data), + 'adunit_code': buildAdUnit(data), + 's': `${size.width}x${size.height}`, + 'latency': getLatency(data), + 'response': response, + 'jsLatency': getLatency(data), + 'buyername': data.bidder.toLowerCase() + }; +} +function sendQueue(auctionId) { + let auction = auctionCache[auctionId]; + let data = auction.queue; + auction.queue = []; + auction.qTimeout = false; + sonobiAdapter.sendData(auction, data); +} +function addToAuctionQueue(auctionId, id) { + let auction = auctionCache[auctionId]; + auction.queue = auction.queue.filter((item) => { + if (item.bidid !== id) { return true; } + return auction.stats[id].data.p !== item.p; + }); + auction.queue.push(utils.deepClone(auction.stats[id].data)); + if (!auction.qTimeout) { + auction.qTimeout = setTimeout(() => { + sendQueue(auctionId); + }, initOptions.delay) + } +} +function updateBidStats(auctionId, id, data) { + let auction = auctionCache[auctionId]; + auction.stats[id].data = {...auction.stats[id].data, ...data}; + addToAuctionQueue(auctionId, id); + logInfo('Updated Bid Stats: ', auction.stats[id]); + return auction.stats[id]; +} + +function handleOtherEvents(eventType, args) { + logInfo('Other Event: ' + eventType, args); +} + +function handlerAuctionInit(args) { + auctionCache[args.auctionId] = buildAuctionEntity(args); + deleteOldAuctions(); + logInfo('Auction Init', args); +} +function handlerBidRequested(args) { + let auction = auctionCache[args.auctionId]; + let data = []; + let phase = 1; + let response = 1; + args.bids.forEach(function (bidRequest) { + auction = auctionCache[bidRequest.auctionId] + let built = buildItem(bidRequest, response, phase); + auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built}; + addToAuctionQueue(args.auctionId, built.bidid); + }) + + logInfo('Bids Requested ', data); +} + +function handlerBidAdjustment(args) { + logInfo('Bid Adjustment', args); +} +function handlerBidderDone(args) { + logInfo('Bidder Done', args); +} + +function handlerAuctionEnd(args) { + let winners = {}; + args.bidsReceived.forEach((bid) => { + if (!winners[bid.adUnitCode]) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } else if (winners[bid.adUnitCode].cpm < bid.cpm) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } + }) + args.adUnitCodes.forEach((adUnitCode) => { + if (winners[adUnitCode]) { + let bidId = winners[adUnitCode].bidId; + updateBidStats(args.auctionId, bidId, {response: 4}); + } + }) + logInfo('Auction End', args); + logInfo('Auction Cache', auctionCache[args.auctionId].stats); +} +function handlerBidWon(args) { + let {auctionId, requestId} = args; + let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); + logInfo('Bid Won ', args); + logInfo('Bid Update Result: ', res); +} +function handlerBidResponse(args) { + let {auctionId, requestId, cpm, size, timeToRespond} = args; + updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); + + logInfo('Bid Response ', args); +} +function handlerBidTimeout(args) { + let {auctionId, bidId} = args; + logInfo('Bid Timeout ', args); + updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); +} +let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { + track({eventType, args}) { + switch (eventType) { + case AUCTION_INIT: + handlerAuctionInit(args); + break; + case BID_REQUESTED: + handlerBidRequested(args); + break; + case BID_ADJUSTMENT: + handlerBidAdjustment(args); + break; + case BIDDER_DONE: + handlerBidderDone(args); + break; + case AUCTION_END: + handlerAuctionEnd(args); + break; + case BID_WON: + handlerBidWon(args); + break; + case BID_RESPONSE: + handlerBidResponse(args); + break; + case BID_TIMEOUT: + handlerBidTimeout(args); + break; + default: + handleOtherEvents(eventType, args); + break; + } + }, + +}); + +sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; + +sonobiAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + logInfo('Analytics adapter enabled', initOptions); + sonobiAdapter.originEnableAnalytics(config); + } +}; + +sonobiAdapter.initConfig = function (config) { + let isCorrectConfig = true; + initOptions = {}; + initOptions.options = utils.deepClone(config.options); + + initOptions.pubId = initOptions.options.pubId || null; + initOptions.siteId = initOptions.options.siteId || null; + initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; + if (!initOptions.pubId) { + logError('"options.pubId" is empty'); + isCorrectConfig = false; + } + if (!initOptions.siteId) { + logError('"options.siteId" is empty'); + isCorrectConfig = false; + } + + initOptions.server = DEFAULT_EVENT_URL; + initOptions.host = initOptions.options.host || window.location.hostname; + this.initOptions = initOptions; + return isCorrectConfig; +}; + +sonobiAdapter.getOptions = function () { + return initOptions; +}; + +sonobiAdapter.sendData = function (auction, data) { + let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; + ajax( + url, + function () { logInfo('Auction [' + auction.id + '] sent ', data); }, + JSON.stringify(data), + { + method: 'POST', + // withCredentials: true, + contentType: 'text/plain' + } + ); +} + +function logInfo(message, meta) { + utils.logInfo(buildLogMessage(message), meta); +} + +function logError(message) { + utils.logError(buildLogMessage(message)); +} + +function buildLogMessage(message) { + return 'Sonobi Prebid Analytics: ' + message; +} + +adapterManager.registerAnalyticsAdapter({ + adapter: sonobiAdapter, + code: 'sonobi' +}); + +export default sonobiAdapter; diff --git a/modules/sonobiAnalyticsAdapter.md b/modules/sonobiAnalyticsAdapter.md new file mode 100644 index 00000000000..1ef46bac833 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +``` +Module Name: Sonobi Analytics Adapter +Module Type: Analytics Adapter +Maintainer: apex@sonobi.com +``` + +# Description + +Module that connects to Sonobi's Analytics service + +# Test Parameters +``` + + pbjs.enableAnalytics({ + provider: 'sonobi', + options: { + pubId: 'ffBB352', + siteId: 57463, + delay: 300 + } + }); +``` diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index bd5f63e5302..b0593e5c7d7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,9 +1,14 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; +import { Renderer } from '../src/Renderer'; +import { VIDEO, BANNER } from '../src/mediaTypes'; + const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = '//sofia.trustx.org/hb'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = '//sofia.trustx.org/push_sync'; +const RENDERER_URL = '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js'; + const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -17,6 +22,7 @@ const LOG_ERROR_MESS = { }; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. * @@ -113,7 +119,7 @@ export const spec = { * @param {*} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; const bidsMap = bidRequest.bidsMap; @@ -128,7 +134,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -155,7 +161,7 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); @@ -168,7 +174,7 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { const slot = awaitingBids[sizeId][0]; const bid = slot.bids.shift(); - bidResponses.push({ + const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, bidderCode: spec.code, cpm: serverBid.price, @@ -178,9 +184,26 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { currency: 'USD', netRevenue: priceType !== 'gross', ttl: TIME_TO_LIVE, - ad: serverBid.adm, dealId: serverBid.dealid - }); + }; + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); + } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + + bidResponses.push(bidResponse); if (!slot.bids.length) { slot.parents.forEach(({parent, key, uid}) => { @@ -206,4 +229,29 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { } } +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams, RendererConst) { + const rendererInst = RendererConst.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + rendererInst.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return rendererInst; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index ca407b0c5e8..d6b660c6248 100755 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: paul@trustx.org # Description Module that connects to TrustX demand source to fetch bids. +TrustX Bid Adapter supports Banner and Video (instream and outstream). # Test Parameters ``` @@ -35,6 +36,18 @@ Module that connects to TrustX demand source to fetch bids. } } ] + },{ + code: 'test-div', + sizes: [[640, 360]], + mediaTypes: { video: {} }, + bids: [ + { + bidder: "trustx", + params: { + uid: 7697 + } + } + ] } ]; ``` \ No newline at end of file diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 728665bb204..7abb94736df 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -21,12 +21,16 @@ export const spec = { const cb = Math.floor(Math.random() * 99999999999); const referrer = encodeURIComponent(utils.getTopWindowUrl()); const bidId = bidRequest.bidId; + const unitCode = bidRequest.adUnitCode; + const timeout = config.getConfig('bidderTimeout'); const payload = { v: 'hb1', p: placementId, cb: cb, r: referrer, uid: bidId, + uc: unitCode, + tmax: timeout, t: 'i' }; diff --git a/package.json b/package.json index cd78a3036b9..d73d9014d99 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.6.0-pre", + "version": "2.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/sizeMapping.js b/src/sizeMapping.js index b8278df69c0..04c9773a1af 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,5 +1,5 @@ import { config } from './config'; -import {logWarn, isPlainObject, deepAccess, deepClone} from './utils'; +import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils'; import includes from 'core-js/library/fn/array/includes'; let sizeConfig = []; @@ -123,7 +123,17 @@ function evaluateSizeConfig(configs) { typeof config === 'object' && typeof config.mediaQuery === 'string' ) { - if (matchMedia(config.mediaQuery).matches) { + let ruleMatch = false; + + try { + ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; + } catch (e) { + logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); + + ruleMatch = matchMedia(config.mediaQuery).matches; + } + + if (ruleMatch) { if (Array.isArray(config.sizesSupported)) { results.shouldFilter = true; } diff --git a/src/utils.js b/src/utils.js index 5e70b31b0b2..a93e25356bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1276,3 +1276,20 @@ export function getMinValueFromArray(array) { export function getMaxValueFromArray(array) { return Math.max(...array); } + +/** + * This function will create compare function to sort on object property + * @param {string} property + * @returns {function} compare function to be used in sorting + */ +export function compareOn(property) { + return function compare(a, b) { + if (a[property] < b[property]) { + return 1; + } + if (a[property] > b[property]) { + return -1; + } + return 0; + } +} diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 9110e1fcdc0..aab1da11653 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -139,7 +139,10 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; - + if (bid.mediaType === 'video') { + expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; + expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; + } if (!keys) { return expected; } @@ -157,8 +160,20 @@ describe('auctionmanager.js', function () { }); it('No bidder level configuration defined - default', function () { - var expected = getDefaultExpected(bid); - var response = getKeyValueTargetingPairs(bid.bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + $$PREBID_GLOBAL$$.bidderSettings = {}; + let expected = getDefaultExpected(bid); + let response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('No bidder level configuration defined - default for video', function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); assert.deepEqual(response, expected); }); @@ -213,6 +228,70 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); + it('Custom configuration for all bidders with video bid', function () { + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: CONSTANTS.TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: CONSTANTS.TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + return bidResponse.pbMg; + } + }, { + key: CONSTANTS.TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.UUID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.CACHE_ID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + } + ] + + } + }; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); + }); + it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = { diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index a2c8125850a..28bccd7844e 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -238,7 +238,7 @@ describe('AdgenerationAdapter', function () { { data: { label: 'optout_url', - value: 'https://supership.jp/optout/' + value: 'https://supership.jp/optout/#' }, id: 502 }, @@ -350,6 +350,7 @@ describe('AdgenerationAdapter', function () { sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', 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'] @@ -396,6 +397,7 @@ describe('AdgenerationAdapter', function () { expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); expect(result.native.body).to.equal(bidResponses.native.native.body); expect(result.native.cta).to.equal(bidResponses.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.native.native.privacyLink); expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl); expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]); expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 16e94fd569f..a47c77d25ac 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -4,7 +4,7 @@ import * as videoCache from 'src/videoCache'; import * as auction from 'src/auction'; import { ADPOD } from 'src/mediaTypes'; -import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig } from 'modules/adpod'; +import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod'; let expect = require('chai').expect; @@ -150,11 +150,13 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal(bidResponse2.adId); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); }); it('should send prebid cache call once bid queue is full', function () { @@ -220,10 +222,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('adId234'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should send prebid cache call after set period of time (even if queue is not full)', function () { @@ -276,6 +280,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { @@ -360,14 +365,17 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('multi_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[2].adId).to.equal('multi_ad3'); expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { @@ -436,10 +444,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('nocat_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { @@ -564,6 +574,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bids to auction if PBC returns an error', function() { @@ -979,4 +990,63 @@ describe('adpod.js', function () { expect(logWarnStub.called).to.equal(false); }) }); + + describe('adpod utils', function() { + it('should sort bids array', function() { + let bids = [{ + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + bids.sort(sortByPricePerSecond); + let sortedBids = [{ + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + expect(bids).to.include.deep.ordered.members(sortedBids); + }); + }) }); diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js old mode 100644 new mode 100755 index f7a49ef995f..fbdfc9f30ee --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -7,9 +7,9 @@ describe('advangelistsBidAdapter', function () { let bidRequestsVid; beforeEach(function () { - bidRequests = [{'bidder': 'avng', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequests = [{'bidder': 'advangelists', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - bidRequestsVid = [{'bidder': 'avng', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; }); describe('spec.isBidRequestValid', function () { @@ -86,14 +86,14 @@ describe('advangelistsBidAdapter', function () { it('should return valid video bid responses', function () { let _mediaTypes = VIDEO; - const avngbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const advangelistsbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com.ar'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} - const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, avngbidreqVid); + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, advangelistsbidreqVid); delete bidResponseVid['vastUrl']; delete bidResponseVid['ad']; expect(bidResponseVid).to.deep.equal({ requestId: bidRequestsVid[0].bidId, - bidderCode: 'avng', + bidderCode: 'advangelists', creativeId: serverResponseVid.seatbid[0].bid[0].crid, cpm: serverResponseVid.seatbid[0].bid[0].price, width: serverResponseVid.seatbid[0].bid[0].w, @@ -106,10 +106,10 @@ describe('advangelistsBidAdapter', function () { }); it('should return valid banner bid responses', function () { - const avngbidreq = {bids: {}}; + const advangelistsbidreq = {bids: {}}; bidRequests.forEach(bid => { let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - avngbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + advangelistsbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] @@ -117,11 +117,11 @@ describe('advangelistsBidAdapter', function () { }); const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, avngbidreq); + const bidResponse = spec.interpretResponse({ body: serverResponse }, advangelistsbidreq); expect(bidResponse).to.deep.equal({ requestId: bidRequests[0].bidId, ad: serverResponse.seatbid[0].bid[0].adm, - bidderCode: 'avng', + bidderCode: 'advangelists', creativeId: serverResponse.seatbid[0].bid[0].crid, cpm: serverResponse.seatbid[0].bid[0].price, width: serverResponse.seatbid[0].bid[0].w, diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 71fb7bc3776..0277e0ab964 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -235,6 +235,14 @@ describe('AdxcgAdapter', function () { 'label': 'SPONSORED', 'value': 'sponsoredByContent' } + }, { + 'id': 5, + 'required': 0, + 'icon': { + 'url': 'iconContent', + 'w': 400, + 'h': 400 + } }], 'link': { 'url': 'linkContent' @@ -307,7 +315,15 @@ describe('AdxcgAdapter', function () { expect(result[0].native.clickUrl).to.equal('linkContent') expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']) expect(result[0].native.title).to.equal('titleContent') - expect(result[0].native.image).to.equal('imageContent') + + expect(result[0].native.image.url).to.equal('imageContent') + expect(result[0].native.image.height).to.equal(600) + expect(result[0].native.image.width).to.equal(600) + + expect(result[0].native.icon.url).to.equal('iconContent') + expect(result[0].native.icon.height).to.equal(400) + expect(result[0].native.icon.width).to.equal(400) + expect(result[0].native.body).to.equal('descriptionContent') expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent') }) diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 00dafcb7b11..7539848d5bd 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -54,30 +54,31 @@ describe('AjaAdapter', function () { expect(requests[0].method).to.equal('GET'); }); }); - describe('interpretResponse', function () { - let response = { - 'is_ad_return': true, - 'ad': { - 'ad_type': 1, - 'prebid_id': '51ef8751f9aead', - 'price': 12.34, - 'currency': 'USD', - 'creative_id': '123abc', - 'banner': { - 'w': 300, - 'h': 250, - 'tag': '
', - 'imps': [ - '//as.amanad.adtdp.com/v1/imp' - ] - } - }, - 'syncs': [ - 'https://example.com' - ] - }; + describe('interpretResponse', function () { it('should get correct banner bid response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 1, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'USD', + 'creative_id': '123abc', + 'banner': { + 'w': 300, + 'h': 250, + 'tag': '
', + 'imps': [ + '//as.amanad.adtdp.com/v1/imp' + ] + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + let expectedResponse = [ { 'requestId': '51ef8751f9aead', @@ -130,6 +131,95 @@ describe('AjaAdapter', function () { expect(result[0]).to.have.property('mediaType', 'video'); }); + it('handles native response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 2, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'JPY', + 'creative_id': '123abc', + 'native': { + 'template_and_ads': { + 'head': '', + 'body_wrapper': '', + 'body': '', + 'ads': [ + { + 'ad_format_id': 10, + 'assets': { + 'ad_spot_id': '123abc', + 'index': 0, + 'adchoice_url': 'https://aja-kk.co.jp/optout', + 'cta_text': 'cta', + 'img_icon': 'https://example.com/img_icon', + 'img_icon_width': '50', + 'img_icon_height': '50', + 'img_main': 'https://example.com/img_main', + 'img_main_width': '200', + 'img_main_height': '100', + 'lp_link': 'https://example.com/lp?k=v', + 'sponsor': 'sponsor', + 'title': 'ad_title', + 'description': 'ad_desc' + }, + 'imps': [ + 'https://example.com/imp' + ], + 'inviews': [ + 'https://example.com/inview' + ], + 'jstracker': '', + 'disable_trimming': false + } + ] + } + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + + let expectedResponse = [ + { + 'requestId': '51ef8751f9aead', + 'cpm': 12.34, + 'creativeId': '123abc', + 'dealId': undefined, + 'mediaType': 'native', + 'currency': 'JPY', + 'ttl': 300, + 'netRevenue': true, + 'native': { + 'title': 'ad_title', + 'body': 'ad_desc', + 'cta': 'cta', + 'sponsoredBy': 'sponsor', + 'image': { + 'url': 'https://example.com/img_main', + 'width': 200, + 'height': 100 + }, + 'icon': { + 'url': 'https://example.com/img_icon', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://example.com/lp?k=v', + 'impressionTrackers': [ + 'https://example.com/imp' + ] + } + } + ]; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}) + expect(result).to.deep.equal(expectedResponse) + }); + it('handles nobid responses', function () { let response = { 'is_ad_return': false, diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js new file mode 100644 index 00000000000..73186250f5b --- /dev/null +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -0,0 +1,283 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { spec } from 'modules/brightcomBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'https://brightcombid.marphezis.com/hb'; + +describe('brightcomBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when tagid not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 832706b2b95..fc5e1d1b45a 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -43,6 +43,10 @@ const REQUEST = { 'bidderRequestId': '109f2a181342a9', 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' }], + 'gdprConsent': { + 'consentString': 'consent-test', + 'gdprApplies': true + }, 'start': 1487883186070, 'auctionStart': 1487883186069, 'timeout': 3000 @@ -52,6 +56,7 @@ const RESPONSE = { 'headers': null, 'body': { 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], 'decisions': { '2b0f82502298c9': { 'adId': 2364764, @@ -206,7 +211,7 @@ describe('Consumable BidAdapter', function () { }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { - let bidRequest = spec.buildRequests(REQUEST.bidRequest); + let bidRequest = spec.buildRequests(REQUEST.bidRequest, REQUEST); let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('consumable'); @@ -264,5 +269,12 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { + let syncOptions = {'pixelEnabled': true}; + let opts = spec.getUserSyncs(syncOptions, [RESPONSE]); + + expect(opts.length).to.equal(1); + }); }); }); diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..95600e84a9f --- /dev/null +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -0,0 +1,209 @@ +import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter'; +import includes from 'core-js/library/fn/array/includes'; +import { expect } from 'chai'; +import { parse as parseURL } from 'src/url'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('finteza analytics adapter', function () { + const clientId = 'fntz-client-32145'; + + let xhr; + let requests; + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => { requests.push(request) }; + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(fntzAnalyticsAdapter, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'finteza', + adapter: fntzAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'finteza', + options: { + id: clientId, // Client ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTrack: 'Bid Response %bidder%', + bidTimeoutTrack: 'Bid Timeout %Bidder%', + bidWonTrack: 'Bid Won %BIDDER%', + } + }); + }); + + afterEach(function () { + xhr.restore(); + events.getEvents.restore(); + fntzAnalyticsAdapter.track.restore(); + fntzAnalyticsAdapter.disableAnalytics(); + }); + + describe('track', () => { + describe('bid request', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const bidRequest = { + bidderCode: bidderCode, + auctionId: pauctionId, + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: bidderCode, + placementCode: 'container-1', + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: pauctionId, + startTime: 1509369418389, + sizes: [[300, 250]], + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Request ${bidderCode.toUpperCase()}`); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid response', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeToRespond = 443; + const cpm = 0.015; + + const bidResponse = { + bidderCode: bidderCode, + adId: '208750227436c1', + cpm: cpm, + auctionId: pauctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: bidderCode, + timeToRespond: timeToRespond, + size: '300x250', + width: 300, + height: 250, + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Response ${bidderCode.toLowerCase()}`); + expect(url.search.c1_value).to.equal(String(timeToRespond)); + expect(url.search.c1_unit).to.equal('ms'); + expect(url.search.c2_value).to.equal(String(cpm)); + expect(url.search.c2_unit).to.equal('usd'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid won', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const cpm = 0.015; + + const bidWon = { + bidderCode: bidderCode, + cpm: cpm, + adId: 'adIdData', + ad: 'adContent', + auctionId: pauctionId, + width: 300, + height: 250 + } + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_WON, bidWon); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Won ${bidderCode.toUpperCase()}`); + expect(url.search.c1_value).to.equal(String(cpm)); + expect(url.search.c1_unit).to.equal('usd'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid timeout', () => { + it('builds and sends data', function () { + const bidderCode = 'biDDer789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeout = 2540; + + const bidTimeout = [ + { + bidId: '208750227436c1', + bidder: bidderCode, + auctionId: pauctionId, + timeout: timeout, + } + ]; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Timeout Bidder789`); + expect(url.search.c1_value).to.equal(String(timeout)); + expect(url.search.c1_unit).to.equal('ms'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + }); +}); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js index 7f1c8857285..5846774c8b1 100644 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -86,7 +86,7 @@ describe('freeWheel adserver module', function() { }); expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(2); + expect(targeting['midroll_1'].length).to.equal(3); }); it('should return targeting for passed adunit code', function() { @@ -128,7 +128,7 @@ describe('freeWheel adserver module', function() { }); expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(2); + expect(targeting['midroll_1'].length).to.equal(3); }); it('should return unique category bids when competitive exclusion is enabled', function() { @@ -139,10 +139,10 @@ describe('freeWheel adserver module', function() { } }); amStub.returns([ - createBid(10, 'preroll_1', 30, '10.00_airline_30s', '123', 'airline'), - createBid(15, 'preroll_1', 30, '15.00_airline_30s', '123', 'airline'), - createBid(15, 'midroll_1', 60, '15.00_travel_60s', '123', 'travel'), - createBid(10, 'preroll_1', 30, '10.00_airline_30s', '123', 'airline') + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395'), + createBid(15, 'preroll_1', 30, '15.00_395_30s', '123', '395'), + createBid(15, 'midroll_1', 60, '15.00_406_60s', '123', '406'), + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395') ]); let targeting; getTargeting({ @@ -157,9 +157,9 @@ describe('freeWheel adserver module', function() { it('should only select bids less than adpod duration', function() { amStub.returns([ - createBid(10, 'preroll_1', 90, '10.00_airline_90s', '123', 'airline'), - createBid(15, 'preroll_1', 90, '15.00_airline_90s', '123', 'airline'), - createBid(15, 'midroll_1', 90, '15.00_travel_90s', '123', 'travel') + createBid(10, 'preroll_1', 90, '10.00_395_90s', '123', '395'), + createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), + createBid(15, 'midroll_1', 90, '15.00_406_90s', '123', '406') ]); let targeting; getTargeting({ @@ -194,11 +194,11 @@ describe('freeWheel adserver module', function() { function getBidsReceived() { return [ - createBid(10, 'preroll_1', 15, '10.00_airline_15s', '123', 'airline'), - createBid(15, 'preroll_1', 15, '15.00_airline_15s', '123', 'airline'), - createBid(15, 'midroll_1', 30, '15.00_travel_30s', '123', 'travel'), - createBid(5, 'midroll_1', 5, '5.00_travel_5s', '123', 'travel'), - createBid(20, 'midroll_1', 60, '20.00_travel_60s', '123', 'travel'), + createBid(10, 'preroll_1', 15, '10.00_395_15s', '123', '395'), + createBid(15, 'preroll_1', 15, '15.00_395_15s', '123', '395'), + createBid(15, 'midroll_1', 30, '15.00_406_30s', '123', '406'), + createBid(5, 'midroll_1', 5, '5.00_406_5s', '123', '406'), + createBid(20, 'midroll_1', 60, '20.00_406_60s', '123', '406'), ] } @@ -225,8 +225,8 @@ function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, 'appnexus': { 'buyerMemberId': 9325 }, - 'vastUrl': 'http://nym1-ib.adnxs.com/ab?ro=1&referrer=http%3A%2F%2Fprebid.org%2Fexamples%2Fvideo%2FjwPlayerPrebid.html&e=wqT_3QKQCKAQBAAAAwDWAAUBCOC2reIFENXVz86_iKrdKRiyjp7_7P7s0GQqNgkAAAECCBRAEQEHNAAAFEAZAAAA4HoUFEAhERIAKREJADERG6gw6dGnBjjtSEDtSEgCUMuBwC5YnPFbYABozbp1eIHdBIABAYoBA1VTRJIBAQbwUJgBAaABAagBAbABALgBA8ABBMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU0ODQ0MjQ2NCk7dWYoJ3InLCA5NzUxNzc3MTYeAPQAAZIC8QEhOXpPdkVBaTItTHdLRU11QndDNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTZkR25CbGdBWUVwb0FIQ0FBWGdBZ0FHMEFvZ0JBSkFCQVpnQkFhQUJBYWdCQTdBQkFMa0I4NjFxcEFBQUZFREJBZk90YXFRQUFCUkF5UUhWSVlsRnN5SDRQOWtCQUFBQUFBQUE4RF9nQVFEMUFRQUFBQUNZQWdDZ0FnQzFBZ0FBQUFDOUFnQUFBQURBQWdESUFnRGdBZ0RvQWdENEFnQ0FBd0dRQXdDWUF3R29BN2I0dkFxNkF3bE9XVTB5T2pRd016SGdBODBGmgJhIU53M1VaUWkyLvQAKG5QRmJJQVFvQURFCY1cQUFVUURvSlRsbE5Nam8wTURNeFFNMEZTBZwYQUFBUEFfVREMDEFBQVcdDPBMwgI_aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc2hvdy12aWRlby13aXRoLWEtZGZwLXZpZGVvLXRhZy5odG1s2AIA4AKtmEjqAjRGSgAgZXhhbXBsZXMvBUUkL2p3UGxheWVyUAlseGh0bWzyAhMKD0NVU1RPTV9NT0RFTF9JRBIA8gIaChYyFgAgTEVBRl9OQU1FAR0IHgoaNh0ACEFTVAE-4ElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw3-8E6YBACiBAsxMC4xLjEyLjE4MKgEjq4IsgQSCAEQAhiABSDoAigBKAIwADgDuAQAwAQAyAQA0gQOOTMyNSNOWU0yOjQwMzHaBAIIAeAEAPAEYTYgiAUBmAUAoAX_EQEUAcAFAMkFaXAU8D_SBQkJCQx4AADYBQHgBQHwBcOVC_oFBAgAEACQBgGYBgC4BgDBBgklJPA_yAYA2gYWChAJEDQAAAAAAAAAAAAAEAAYAA..&s=539bcaeb9ce05a13a8c4a6cab3c000194a8e8f53', - 'vastImpUrl': 'http://nym1-ib.adnxs.com/vast_track/v2?info=ZQAAAAMArgAFAQlgW0tcAAAAABHV6tP5Q6i6KRlgW0tcAAAAACDLgcAuKAAw7Ug47UhA0-hISLuv1AFQ6dGnBljDlQtiAkZSaAFwAXgAgAEBiAEBkAGABZgB6AKgAQCoAcuBwC4.&s=61db1767c8c362ef1a58d2c5587dd6a9b1015aeb&event_type=1', + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', 'responseTimestamp': 1548442460888, 'requestTimestamp': 1548442460827, diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 6ef04ddb9d9..8e12a3bb7e4 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1190,6 +1190,10 @@ describe('OpenxAdapter', function () { expect(bid.ts).to.equal(adUnitOverride.ts); }); + it('should return a brand ID', function () { + expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + }); + it('should register a beacon', function () { resetBoPixel(); spec.interpretResponse({body: bidResponse}, bidRequest); diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 29c6c2c6d9a..0761ed8d31e 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -64,6 +64,21 @@ describe('orbidderBidAdapter', () => { delete bidRequest.params; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + + it('accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 123; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + + bidRequest.params.bidfloor = 1.23; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('doesn\'t accept malformed bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 'another not usable string'; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); }); describe('buildRequests', () => { diff --git a/test/spec/modules/otmBidAdapter_spec.js b/test/spec/modules/otmBidAdapter_spec.js index 0fe81a512cb..f3a98d43e57 100644 --- a/test/spec/modules/otmBidAdapter_spec.js +++ b/test/spec/modules/otmBidAdapter_spec.js @@ -29,6 +29,44 @@ describe('otmBidAdapterTests', function () { expect(req_data.bidid).to.equal('bid1234'); }); + it('validate_best_size_select', function () { + // when: + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[300, 500], [300, 600], [240, 400], [300, 50]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(240); + expect(req_data.h).to.equal(400); + + // when: + bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[200, 240], [400, 440]] + }]; + + request = spec.buildRequests(bidRequestData); + req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(200); + expect(req_data.h).to.equal(240); + }); + it('validate_response_params', function () { let bidRequestData = { data: { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index c404d9003fe..a910ef391b0 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -1,8 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/ozoneBidAdapter'; - +import { config } from 'src/config'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; -// const OZONEURI = 'https://www.1in39.co.uk/openrtb2/auction'; const BIDDER_CODE = 'ozone'; /* @@ -23,6 +22,19 @@ var validBidRequests = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsMinimal = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; var validBidRequestsNoSizes = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -37,7 +49,7 @@ var validBidRequestsNoSizes = [ } ]; -var validBidRequestsWithMediaTypes = [ +var validBidRequestsWithBannerMediaType = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -51,6 +63,20 @@ var validBidRequestsWithMediaTypes = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsWithNonBannerMediaTypes = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + mediaTypes: {video: {info: 'dummy data'}, native: {info: 'dummy data'}}, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; var validBidderRequest = { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -152,11 +178,11 @@ describe('ozone Adapter', function () { params: { placementId: '1310000099', publisherId: '9876abcd12-3', - siteId: '1234567890' + siteId: '1234567890', + customData: {'gender': 'bart', 'age': 'low'}, + ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, + lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, }, - customData: {'gender': 'bart', 'age': 'low'}, - ozoneData: {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}, - lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, siteId: 1234567890 } @@ -456,13 +482,35 @@ describe('ozone Adapter', function () { expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); + it('adds all parameters inside the ext object only', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.ozoneData).to.be.an('object'); + expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + expect(request).not.to.have.key('ozoneData'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + it('has correct bidder', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest); expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); }); it('handles mediaTypes element correctly', function () { - const request = spec.buildRequests(validBidRequestsWithMediaTypes, validBidderRequest); + const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles no ozone, lotame or custom data', function () { + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles missing banner mediaType element correctly', function () { + const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypes, validBidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); @@ -470,6 +518,37 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'ozone', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = validBidRequests; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.user.ext).to.exist; + expect(payload.user.ext.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); + }); + + it('should be able to handle non-single requests', function () { + config.setConfig({'ozone': {'singleRequest': false}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.be.a('array'); + expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + // need to reset the singleRequest config flag: + config.setConfig({'ozone': {'singleRequest': true}}); + }); }); describe('interpretResponse', function () { @@ -495,5 +574,26 @@ describe('ozone Adapter', function () { const result = spec.interpretResponse(validResponse, request); expect(result.length).to.equal(1); }); + it('should fail ok if no seatbid in server response', function () { + const result = spec.interpretResponse({}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + it('should fail ok if seatbid is not an array', function () { + const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + }); + + describe('userSyncs', function () { + it('should fail gracefully if no server response', function () { + const result = spec.getUserSyncs('bad', false); + expect(result).to.be.empty; + }); + it('should fail gracefully if server response is empty', function () { + const result = spec.getUserSyncs('bad', []); + expect(result).to.be.empty; + }); }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 912a6696bdd..0042ef5211e 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -281,10 +281,13 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77d', 'price': 1.3, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['blackrock.com'], 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 6 + 'deal_channel': 6, + 'advid': 976, + 'dspid': 123 } }] }, { @@ -293,10 +296,13 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77e', 'price': 1.7, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['hivehome.com'], 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 5 + 'deal_channel': 5, + 'advid': 832, + 'dspid': 422 } }] }] @@ -1323,6 +1329,9 @@ describe('PubMatic adapter', function () { expect(response[0].currency).to.equal('USD'); expect(response[0].netRevenue).to.equal(false); expect(response[0].ttl).to.equal(300); + expect(response[0].meta.networkId).to.equal(123); + expect(response[0].meta.buyerId).to.equal(976); + expect(response[0].meta.clickUrl).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); @@ -1339,6 +1348,9 @@ describe('PubMatic adapter', function () { expect(response[1].currency).to.equal('USD'); expect(response[1].netRevenue).to.equal(false); expect(response[1].ttl).to.equal(300); + expect(response[1].meta.networkId).to.equal(422); + expect(response[1].meta.buyerId).to.equal(832); + expect(response[1].meta.clickUrl).to.equal('hivehome.com'); expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); }); diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js new file mode 100644 index 00000000000..8f1fb9efc1e --- /dev/null +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -0,0 +1,109 @@ +import { expect } from 'chai' +import { spec, _getPlatform } from 'modules/smartrtbBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +describe('SmartRTBBidAdapter', function () { + const adapter = newBidder(spec) + + let bidRequest = { + bidId: '123', + transactionId: '456', + sizes: [[ 300, 250 ]], + params: { + pubId: 123, + medId: 'm_00a95d003340dbb2fcb8ee668a84fa', + zoneId: 'z_261b6c7e7d4d4985393b293cc903d1' + } + } + + describe('codes', function () { + it('should return a bidder code of smartrtb', function () { + expect(spec.code).to.equal('smartrtb') + }) + it('should alias smrtb', function () { + expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true + }) + }) + + describe('isBidRequestValid', function () { + it('should return true if all params present', function () { + expect(spec.isBidRequestValid(bidRequest)).to.be.true + }) + + it('should return false if any parameter missing', function () { + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { pubId: null } }))).to.be.false + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { medId: null } }))).to.be.false + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { zoneId: null } }))).to.be.false + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = JSON.parse(req.data) + expect(rdata.imps.length).to.equal(1) + }) + + it('should include all publisher params', function () { + let r = rdata.imps[0] + expect(r.pub_id !== null && r.med_id !== null && r.zone_id !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + it('should form compliant bid object response', function () { + let res = { + body: { + bids: [{ + bid_id: '123', + cpm: 1.23, + w: 300, + h: 250, + html: 'deadbeef', + crid: 'crid' + }] + } + } + + let ir = spec.interpretResponse(res, bidRequest) + + expect(ir.length).to.equal(1) + + let en = ir[0] + + expect(en.requestId != null && + en.cpm != null && typeof en.cpm === 'number' && + en.width != null && typeof en.width === 'number' && + en.height != null && typeof en.height === 'number' && + en.ad != null && + en.creativeId != null + ).to.be.true + }) + }) + + describe('getUserSyncs', function () { + it('should return iframe sync', function () { + let sync = spec.getUserSyncs({ iframeEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + + it('should return pixel sync', function () { + let sync = spec.getUserSyncs({ pixelEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'image') + expect(typeof sync[0].url === 'string') + }) + }) +}) diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js new file mode 100644 index 00000000000..489f393523a --- /dev/null +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -0,0 +1,191 @@ +import { expect } from 'chai'; +import { spec } from 'modules/smilewantedBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; +import { requestBidsHook } from 'modules/consentManagement'; + +// Default params with optional ones +describe('smilewantedBidAdapterTests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: '1234', + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' + }]; + + var BID_RESPONSE = { + body: { + cpm: 3, + width: 300, + height: 250, + creativeId: 'crea_sw_1', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + adUrl: 'https://www.smilewanted.com', + ad: '< --- sw script --- >', + cSyncUrl: 'https://csync.smilewanted.com' + } + }; + + it('SmileWanted - Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('zoneId').and.to.equal('1234'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.50); + 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('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + }); + + it('SmileWanted - Verify build request with referrer', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, { + refererInfo: { + referer: 'http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); + }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('SmileWanted - Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + + it('SmileWanted - Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + }); + + it('SmileWanted - Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.adUrl).to.equal('https://www.smilewanted.com'); + expect(bid.ad).to.equal('< --- sw script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crea_sw_1'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify bidder code', function () { + expect(spec.code).to.equal('smilewanted'); + }); + + it('SmileWanted - Verify bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(2); + expect(spec.aliases[0]).to.equal('smile'); + expect(spec.aliases[1]).to.equal('sw'); + }); + + it('SmileWanted - Verify if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + zoneId: 1234 + } + })).to.equal(true); + }); + + it('SmileWanted - Verify if params(zoneId) is not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + }); + + it('SmileWanted - Verify user sync', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); +}); diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..389dfee34f9 --- /dev/null +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -0,0 +1,89 @@ +import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter'; +import {expect} from 'chai'; +let events = require('src/events'); +let adapterManager = require('src/adapterManager').default; +let constants = require('src/constants.json'); + +describe('Sonobi Prebid Analytic', function () { + let xhr; + let requests = []; + var clock; + + describe('enableAnalytics', function () { + beforeEach(function () { + requests = []; + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(Date.now()); + }); + + afterEach(function () { + xhr.restore(); + events.getEvents.restore(); + clock.restore(); + }); + + after(function () { + sonobiAnalytics.disableAnalytics(); + }); + + it('should catch all events', function (done) { + const initOptions = { + pubId: 'A3B254F', + siteId: '1234', + delay: 100 + }; + + sonobiAnalytics.enableAnalytics(initOptions) + + const bid = { + bidderCode: 'sonobi_test_bid', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '1234', + auctionId: '13', + responseTimestamp: 1496410856397, + requestTimestamp: 1496410856295, + cpm: 1.13, + bidder: 'sonobi', + adUnitCode: 'dom-sample-id', + timeToRespond: 100, + placementCode: 'placementtest' + }; + + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'sonobi', + options: initOptions + }); + + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now()}); + + expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); + expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); + expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); + // Step 3: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); + + // Step 4: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bid); + + // Step 5: Send bid won event + events.emit(constants.EVENTS.BID_WON, bid); + + // Step 6: Send bid timeout event + events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: '13'}); + + // Step 7: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); + + clock.tick(5000); + expect(requests).to.have.length(1); + expect(JSON.parse(requests[0].requestBody)).to.have.length(3) + done(); + }); + }); +}); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 207d3a068ba..de0d1c8a530 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -194,6 +194,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -251,6 +252,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -264,6 +266,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -277,6 +280,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -404,6 +408,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -417,6 +422,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -430,6 +436,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 3
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -443,6 +450,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 4
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -504,6 +512,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 1
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, @@ -517,6 +526,7 @@ describe('TrustXAdapter', function () { 'ad': '
test content 2
', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -526,4 +536,201 @@ describe('TrustXAdapter', function () { expect(result).to.deep.equal(expectedResponse); }); }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57dfefb80eca', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e893c787c22dd', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '57dfefb80eca', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should have right renderer in the bid response', function () { + const spySetRenderer = sinon.spy(); + const stubRenderer = { + setRender: spySetRenderer + }; + const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); + const stubRendererConst = { + install: spyRendererInstall + }; + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e6e65553fc8', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'mediaTypes': { + 'video': { + 'context': 'outstream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c8fdcb3f269f', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3' + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '52' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '1de036c37685', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'renderer': {} + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': 'e6e65553fc8', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': 'c8fdcb3f269f', + 'cpm': 1.00, + 'creativeId': 51, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': '1de036c37685', + 'cpm': 1.20, + 'creativeId': 52, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); + + expect(spySetRenderer.calledTwice).to.equal(true); + expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); + expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); + + expect(spyRendererInstall.calledTwice).to.equal(true); + expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ + id: 'e6e65553fc8', + url: '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js', + loaded: false + }); + expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ + id: 'c8fdcb3f269f', + url: '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js', + loaded: false + }); + + expect(result).to.deep.equal(expectedResponse); + }); }); diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index e064b90f059..e91eeab1369 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -88,6 +88,11 @@ describe('yieldoneBidAdapter', function() { expect(request[0].data.w).to.equal('300'); expect(request[0].data.h).to.equal('250'); }); + + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.uc).to.equal('adunit-code1'); + expect(request[1].data.uc).to.equal('adunit-code2'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index aca4ccf8f54..f85da4cba0b 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -60,6 +60,13 @@ describe('sizeMapping', function () { matchMediaOverride = {matches: false}; + sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake((...args) => { + if (typeof matchMediaOverride === 'function') { + return matchMediaOverride.apply(utils.getWindowTop(), args); + } + return matchMediaOverride; + }); + sandbox.stub(window, 'matchMedia').callsFake((...args) => { if (typeof matchMediaOverride === 'function') { return matchMediaOverride.apply(window, args);