diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 0519cbb7b6e..84131c177a3 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -38,6 +38,10 @@ General gulp commands include separate commands for serving the codebase on a bu Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/bidder-adaptor.html Follow steps above for general review process. In addition, please verify the following: +- Verify the biddercode and aliases are valid: + - Lower case alphanumeric with the only special character allowed is underscore. + - The bidder code should be unique for the first 6 characters + - Reserved words that cannot be used as bidder names: all, context, data, general, prebid, and skadn - Verify that bidder has submitted valid bid params and that bids are being received. - Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. - Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. diff --git a/README.md b/README.md index 3e541080bf9..23fc8342bc5 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,12 @@ Or for Babel 6: Then you can use Prebid.js as any other npm depedendency ```javascript -import prebid from 'prebid.js'; +import pbjs from 'prebid.js'; import 'prebid.js/modules/rubiconBidAdapter'; // imported modules will register themselves automatically with prebid import 'prebid.js/modules/appnexusBidAdapter'; -prebid.processQueue(); // required to process existing pbjs.queue blocks and setup any further pbjs.queue execution +pbjs.processQueue(); // required to process existing pbjs.queue blocks and setup any further pbjs.queue execution -prebid.requestBids({ +pbjs.requestBids({ ... }) @@ -112,11 +112,11 @@ prebid.requestBids({ $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js - $ npm install + $ npm ci *Note:* You need to have `NodeJS` 12.16.1 or greater installed. -*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm install`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. +*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm ci`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. You can check if this is installed by running `gulp -v` and seeing the version that's listed in the `CLI` field of the output. If you have the `gulp` package installed globally, it's likely the same version that you'll see in the `Local` field. If you already have `gulp-cli` installed, it should be a lower major version (it's at version `2.0.1` at the time of the transition). @@ -265,7 +265,7 @@ directory you will have sourcemaps available in your browser's developer tools. To run the example file, go to: -+ `http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html` ++ `http://localhost:9999/integrationExamples/gpt/hello_world.html` As you make code changes, the bundles will be rebuilt and the page reloaded automatically. diff --git a/browsers.json b/browsers.json index 91e0548d78a..f73bbbb8aac 100644 --- a/browsers.json +++ b/browsers.json @@ -23,11 +23,11 @@ "device": null, "os": "Windows" }, - "bs_chrome_80_windows_10": { + "bs_chrome_89_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "80.0", + "browser_version": "89.0", "device": null, "os": "Windows" }, diff --git a/gulpfile.js b/gulpfile.js index 51536992bd0..9bf378779d1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,7 +31,7 @@ const execa = require('execa'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '\nModules: <%= modules %> */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '*/\n'; var port = 9999; const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; @@ -134,6 +134,12 @@ function watch(done) { done(); }; +function makeModuleList(modules) { + return modules.map(module => { + return '"' + module + '"' + }); +} + function makeDevpackPkg() { var cloned = _.cloneDeep(webpackConfig); cloned.devtool = 'source-map'; @@ -145,6 +151,7 @@ function makeDevpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) + .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } @@ -157,16 +164,20 @@ function makeWebpackPkg() { const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); - const modulesString = (externalModules.length > 0) ? externalModules.join(', ') : 'All available modules in current version.'; return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) .pipe(uglify()) - .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid, modules: modulesString }))) + .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) + .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid}))) .pipe(gulp.dest('build/dist')); } +function getModulesListToAddInBanner(modules){ + return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; +} + function gulpBundle(dev) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } @@ -216,6 +227,8 @@ function bundle(dev, moduleArr) { return gulp.src( entries ) + // Need to uodate the "Modules: ..." section in comment with the current modules list + .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { @@ -280,7 +293,7 @@ function test(done) { } else { var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + var browserOverride = helpers.parseBrowserArgs(argv); if (browserOverride.length > 0) { karmaConf.browsers = browserOverride; } diff --git a/integrationExamples/gpt/adUnitFloors.html b/integrationExamples/gpt/adUnitFloors.html new file mode 100644 index 00000000000..bb48a20ef78 --- /dev/null +++ b/integrationExamples/gpt/adUnitFloors.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html new file mode 100644 index 00000000000..33f8b9be6a2 --- /dev/null +++ b/integrationExamples/gpt/adloox.html @@ -0,0 +1,211 @@ + + + + Prebid Display/Video Merged Auction with Adloox Integration + + + + + + + + +

Prebid Display/Video Merged Auction with Adloox Integration

+ +

div-1

+
+ +
+ +

div-2

+
+ +
+ +

video-1

+
+ + + + diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index aef8b7f1654..04d4736c631 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,9 +1,4 @@ - - - - + - + + + - +

Prebid.js Test

Div-1
diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/haloRtdProvider_example.html index 7f9a34e55ee..14debbd2698 100644 --- a/integrationExamples/gpt/haloRtdProvider_example.html +++ b/integrationExamples/gpt/haloRtdProvider_example.html @@ -40,27 +40,6 @@ googletag.pubads().disableInitialLoad(); }); - var appnexusSegmentHandler = function(bid, segments) { - if (!bid.params) { - bid.params = {}; - } - if (!bid.params.user) { - bid.params.user = {}; - } - - if (!Array.isArray(bid.params.user.segments)) { - bid.params.user.segments = []; - } - - var appnexusSegments = []; - for (var i = 0; i < segments.length; i++) { - var segment = segments[i]; - let appnexusSegment = {'id': segment.id, 'value': segment.value}; - appnexusSegments.push(appnexusSegment); - } - bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); - }; - pbjs.que.push(function() { pbjs.setConfig({ debug: true, @@ -71,9 +50,6 @@ name: "halo", waitForIt: true, params: { - mapSegments: { - appnexus: appnexusSegmentHandler // pass true to use the builtin handler. here, we will demo overriding the handler with a function - }, segmentCache: false, requestParams: { publisherId: 0 @@ -89,7 +65,7 @@ }); function sendAdserverRequest() { - document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].params.user.segments); + document.getElementById('audigent_segments').innerHTML = window.localStorage.getItem('auHaloRtd'); document.getElementById('halo_id').innerHTML = testHaloId; if (pbjs.adserverRequestSent) return; @@ -130,7 +106,7 @@ -

Audigent Segments Prebid

+

Halo RTD Prebid

+ + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

+ + + + + + + + + + + diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 75eb85a2d8c..41c27b70ece 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -10,32 +10,30 @@ var PREBID_TIMEOUT = 1000; var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - fpd: { - context: { - data: { - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + code: 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: '123', + mediaID: 'abc' + } + } } - }, - } - }, - - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] }]; var pbjs = pbjs || {}; diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index 0814dcece5b..a06430bcdfa 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -50,7 +50,7 @@ params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -64,7 +64,7 @@ area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -78,12 +78,21 @@ { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -127,13 +136,13 @@ pbjs.setConfig({ debug: true, realTimeData: { - auctionDelay: 50, // maximum time for RTD modules to respond + auctionDelay: 80, // maximum time for RTD modules to respond dataProviders: [ { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500, overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html index 7761178efa8..37902edd979 100644 --- a/integrationExamples/gpt/prebidServer_example.html +++ b/integrationExamples/gpt/prebidServer_example.html @@ -56,10 +56,10 @@ s2sConfig : { accountId : '1', enabled : true, //default value set to false + defaultVendor: 'appnexus', bidders : ['appnexus'], timeout : 1000, //default value is 1000 adapter : 'prebidServer', //if we have any other s2s adapter, default value is s2s - endpoint : 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' } }); diff --git a/integrationExamples/gpt/sirdataRtdProvider_example.html b/integrationExamples/gpt/sirdataRtdProvider_example.html new file mode 100644 index 00000000000..444c9133905 --- /dev/null +++ b/integrationExamples/gpt/sirdataRtdProvider_example.html @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + +
+

+Give consent or make a choice in Europe. Module will add key/value pairs in ad calls. Check out for sd_rtd key in Google Ad call (https://securepubads.g.doubleclick.net/gampad/ads...) and in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : tags[0].keywords.key[sd_rtd] should have an array of string as value. This array will mix user segments and/or page categories based on user's choices. +

+
+

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 71299a4a6e1..9baed794c7a 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -1,337 +1,313 @@ - - - User ID Library Example - + + - - - - - },{ - name: "parrableId", - params: { - // change to Parrable Partner Client ID(s) you received from the Parrable Partners you are using - partner: '30182847-e426-4ff9-b2b5-9ca1324ea09b' - } - }, { - name: "pubCommonId", - storage: { - type: "cookie", - name: "pubcid", - expires: 365 - }, - // value: { - // foo: '9879878907987', - // bar:'93939' - // } - }, { - name: 'identityLink', - params: { - pid: '14' // Set your real identityLink placement ID here - }, - storage: { - type: 'cookie', - name: 'idl_env', - expires: 30 - } - }, - { - name: "sharedId", - // bidders: ["rubicon", "sampleBidders"], // to allow this ID for specific bidders - params: { - syncTime: 60 // in seconds, default is 24 hours - }, - storage: { - type: "cookie", - name: "sharedid", - expires: 28 - } - }, - { - name: 'lotamePanoramaId' - }, - { - name: "liveIntentId", - params: { - publisherId: "9896876" - }, - storage: { - type: "cookie", - name: "_li_pbid", - expires: 28 - } - }, - { - name: "zeotapIdPlus" - }, - { - name: 'haloId', - storage: { - type: "cookie", - name: "haloId", - expires: 28 - } - }, - { - name: "quantcastId" - }, - { - name: "criteo" - }, - ], - syncDelay: 5000, - auctionDelay: 1000 - } - }); - pbjs.addAdUnits(adUnits); - pbjs.requestBids({ - bidsBackHandler: sendAdserverRequest - }); - }); + - function sendAdserverRequest() { - if (pbjs.adserverRequestSent) return; - pbjs.adserverRequestSent = true; - googletag.cmd.push(function() { - pbjs.que.push(function() { - pbjs.setTargetingForGPTAsync(); - googletag.pubads().refresh(); - }); - }); - } + + - setTimeout(function() { - sendAdserverRequest(); - }, FAILSAFE_TIMEOUT); - + +

User ID Modules Example

- +

Generated EIDs

+ +

 
-        
-    
+  

Ad Slot

+
+ +
+ - -

Rubicon Project Prebid

-
- -
- diff --git a/integrationExamples/mass/index.html b/integrationExamples/mass/index.html new file mode 100644 index 00000000000..3b034957d13 --- /dev/null +++ b/integrationExamples/mass/index.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + +
+

Note: for this example to work, you need access to a bid simulation tool from your MASS enabled Exchange partner.

+
+ +
+
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index fc69dd276a3..7ad4bedde5c 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -16,11 +16,17 @@ "zeotapIdPlusIdSystem", "haloIdSystem", "quantcastIdSystem", + "deepintentDpesIdSystem", + "nextrollIdSystem", "idxIdSystem", "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", - "tapadIdSystem" + "mwOpenLinkIdSystem", + "tapadIdSystem", + "novatiqIdSystem", + "uid2IdSystem", + "admixerIdSystem" ], "adpod": [ "freeWheelAdserverVideo", @@ -31,6 +37,7 @@ "haloRtdProvider", "jwplayerRtdProvider", "reconciliationRtdProvider", - "geoedgeRtdProvider" + "geoedgeRtdProvider", + "sirdataRtdProvider" ] } diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 65df8baad2e..e5a6d148808 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -194,18 +194,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl // therefore in ad targetting process ttxRequest.id = bidRequest.bidId; - // Set GDPR related fields - ttxRequest.user = { - ext: { - consent: gdprConsent.consentString - } - }; - ttxRequest.regs = { - ext: { - gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0, - us_privacy: uspConsent || null - } - }; + if (gdprConsent.consentString) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'consent', + gdprConsent.consentString + ) + } + + if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'eids', + bidRequest.userIdAsEids + ) + } + + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'gdpr', + Number(gdprConsent.gdprApplies) + ); + + if (uspConsent) { + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'us_privacy', + uspConsent + ) + } + ttxRequest.ext = { ttx: { prebidStartedAt: Date.now(), @@ -217,11 +235,11 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; if (bidRequest.schain) { - ttxRequest.source = { - ext: { - schain: bidRequest.schain - } - } + ttxRequest.source = setExtension( + ttxRequest.source, + 'schain', + bidRequest.schain + ) } // Finally, set the openRTB 'test' param if this is to be a test bid @@ -250,6 +268,15 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } } +// BUILD REQUESTS: SET EXTENSIONS +function setExtension(obj = {}, key, value) { + return Object.assign({}, obj, { + ext: Object.assign({}, obj.ext, { + [key]: value + }) + }); +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index b7d8722e9f9..1961dba1f10 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -70,8 +70,7 @@ export const spec = { if (response.cpm > 0) { const bidResponse = { requestId: response.id, - creativeId: response.id, - adId: response.id, + creativeId: response.crid || response.id, cpm: response.cpm, width: response.width, height: response.height, diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 87c40db51e6..689e7d02124 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -6,8 +6,8 @@ import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'adWMG'; -const ENDPOINT = 'https://rtb.adwmg.com/prebid'; -let SYNC_ENDPOINT = 'https://rtb.adwmg.com/cphb.html?'; +const ENDPOINT = 'https://hb.adwmg.com/hb'; +let SYNC_ENDPOINT = 'https://hb.adwmg.com/cphb.html?'; export const spec = { code: BIDDER_CODE, @@ -34,11 +34,21 @@ export const spec = { const additional = spec.parseUserAgent(ua); return validBidRequests.map(bidRequest => { + const checkFloorValue = (value) => { + if (isNaN(parseFloat(value))) { + return 0; + } else return parseFloat(value); + } + const adUnit = { code: bidRequest.adUnitCode, bids: { bidder: bidRequest.bidder, - params: bidRequest.params + params: { + publisherId: bidRequest.params.publisherId, + IABCategories: bidRequest.params.IABCategories || [], + floorCPM: bidRequest.params.floorCPM ? checkFloorValue(bidRequest.params.floorCPM) : 0 + } }, mediaTypes: bidRequest.mediaTypes }; @@ -295,7 +305,11 @@ export const spec = { } } - return {devicetype: detectDevice(), os: detectOs().os, osv: detectOs().osv} + return { + devicetype: detectDevice(), + os: detectOs().os, + osv: detectOs().osv + } } -} +}; registerBidder(spec); diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6f7feec59c9..134303bf400 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,22 +8,22 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.6.0'; +export const VERSION = '2.8.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; export const storage = getStorageManager(GVLID, 'adagio'); export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; - +export const MAX_SESS_DURATION = 30 * 60 * 1000; export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl @@ -62,6 +62,8 @@ export const ORTB_VIDEO_PARAMS = { let currentWindow; +const EXT_DATA = {} + export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { @@ -108,6 +110,9 @@ export function getAdagioScript() { // It's an antipattern regarding the TCF2 enforcement logic // but it's the only way to respect the user choice update. window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + // Extra data from external script. + // This key is removed only if localStorage is not accessible. + window.localStorage.removeItem('adagio'); } }); } @@ -131,6 +136,37 @@ function isSafeFrameWindow() { return !!(ws.$sf && ws.$sf.ext); } +// Get localStorage "adagio" data to be passed to the request +export function prepareExchange(storageValue) { + const adagioStorage = JSON.parse(storageValue, function(name, value) { + if (!name.startsWith('_') || name === '') { + return value; + } + }); + let random = utils.deepAccess(adagioStorage, 'session.rnd'); + let newSession = false; + + if (internal.isNewSession(adagioStorage)) { + newSession = true; + random = Math.random(); + } + + const data = { + session: { + new: newSession, + rnd: random + } + } + + utils.mergeDeep(EXT_DATA, adagioStorage, data); + + internal.enqueue({ + action: 'session', + ts: Date.now(), + data: EXT_DATA + }); +} + function initAdagio() { if (canAccessTopWindow()) { currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf(); @@ -146,6 +182,14 @@ function initAdagio() { w.ADAGIO.versions.adagioBidderAdapter = VERSION; w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); + storage.getDataFromLocalStorage('adagio', (storageData) => { + try { + internal.prepareExchange(storageData); + } catch (e) { + utils.logError(LOG_PREFIX, e); + } + }); + getAdagioScript(); } @@ -561,6 +605,21 @@ function isRendererPreferredFromPublisher(bidRequest) { ); } +/** + * + * @param {object} adagioStorage + * @returns {boolean} + */ +function isNewSession(adagioStorage) { + const now = Date.now(); + const { lastActivityTime, vwSmplg } = utils.deepAccess(adagioStorage, 'session', {}); + return ( + !utils.isNumber(lastActivityTime) || + !utils.isNumber(vwSmplg) || + (now - lastActivityTime) > MAX_SESS_DURATION + ) +} + export const internal = { enqueue, getOrAddAdagioAdUnit, @@ -577,7 +636,9 @@ export const internal = { getCurrentWindow, supportIObs, canAccessTopWindow, - isRendererPreferredFromPublisher + isRendererPreferredFromPublisher, + isNewSession, + prepareExchange }; function _getGdprConsent(bidderRequest) { @@ -687,6 +748,112 @@ function _renderer(bid) { }); } +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } + + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } + + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); + + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; + } + if (Array.isArray(bid.admNative.link.clickTrackers)) { + native.clickTrackers = bid.admNative.link.clickTrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } + } + + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } + + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } + } + + bid.native = native +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -812,6 +979,7 @@ export const spec = { site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], + data: EXT_DATA, regs: { gdpr: gdprConsent, coppa: coppa, @@ -873,6 +1041,10 @@ export const spec = { } } + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index aa79338d79e..46656d88d37 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,8 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -78,8 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. @@ -91,6 +91,58 @@ Connects to Adagio demand source to fetch bids. } } }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false + } + } + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + native: { + context: 1, + plcmttype: 2 + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] } ]; diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js new file mode 100644 index 00000000000..18cafe829b5 --- /dev/null +++ b/modules/addefendBidAdapter.js @@ -0,0 +1,79 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'addefend'; + +export const spec = { + code: BIDDER_CODE, + hostname: 'https://addefend-platform.com', + + getHostname() { + return this.hostname; + }, + isBidRequestValid: function(bid) { + return (bid.sizes !== undefined && bid.bidId !== undefined && bid.params !== undefined && + (bid.params.pageId !== undefined && (typeof bid.params.pageId === 'string')) && + (bid.params.placementId !== undefined && (typeof bid.params.placementId === 'string'))); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let bid = { + v: $$PREBID_GLOBAL$$.version, + auctionId: false, + pageId: false, + gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', + referer: bidderRequest.refererInfo.referer, + bids: [], + }; + + for (var i = 0; i < validBidRequests.length; i++) { + let vb = validBidRequests[i]; + let o = vb.params; + bid.auctionId = vb.auctionId; + o.bidId = vb.bidId; + o.transactionId = vb.transactionId; + o.sizes = []; + if (o.trafficTypes) { + bid.trafficTypes = o.trafficTypes; + } + delete o.trafficTypes; + + bid.pageId = o.pageId; + delete o.pageId; + + if (vb.sizes && Array.isArray(vb.sizes)) { + for (var j = 0; j < vb.sizes.length; j++) { + let s = vb.sizes[j]; + if (Array.isArray(s) && s.length == 2) { + o.sizes.push(s[0] + 'x' + s[1]); + } + } + } + bid.bids.push(o); + } + return [{ + method: 'POST', + url: this.getHostname() + '/bid', + options: { withCredentials: true }, + data: bid + }]; + }, + interpretResponse: function(serverResponse, request) { + const requiredKeys = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'advertiserDomains']; + const validBidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && (serverResponse.length > 0)) { + serverResponse.forEach((bid) => { + const bidResponse = {}; + for (const requiredKey of requiredKeys) { + if (!bid.hasOwnProperty(requiredKey)) { + return []; + } + bidResponse[requiredKey] = bid[requiredKey]; + } + validBidResponses.push(bidResponse); + }); + } + return validBidResponses; + } +} + +registerBidder(spec); diff --git a/modules/addefendBidAdapter.md b/modules/addefendBidAdapter.md new file mode 100644 index 00000000000..f1ac3ff2c46 --- /dev/null +++ b/modules/addefendBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdDefend Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@addefend.com +``` + +# Description + +Module that connects to AdDefend as a demand source. + +## Parameters +| Param | Description | Optional | Default | +| ------------- | ------------- | ----- | ----- | +| pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - | +| placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - | +| trafficTypes | comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
| yes | ADBLOCK | + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[970, 250]], // a display size + } + }, + bids: [ + { + bidder: "addefend", + params: { + pageId: "887", + placementId: "9398", + trafficTypes: "ADBLOCK" + } + } + ] + } + ]; +``` diff --git a/modules/adformOpenRTBBidAdapter.js b/modules/adfBidAdapter.js similarity index 97% rename from modules/adformOpenRTBBidAdapter.js rename to modules/adfBidAdapter.js index 3270fb5865a..8b3550e6108 100644 --- a/modules/adformOpenRTBBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -10,8 +10,9 @@ import { import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; -const BIDDER_CODE = 'adformOpenRTB'; +const BIDDER_CODE = 'adf'; const GVLID = 50; +const BIDDER_ALIAS = [ { code: 'adformOpenRTB', gvlid: GVLID } ]; const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; const NATIVE_PARAMS = { title: { @@ -47,6 +48,7 @@ const NATIVE_PARAMS = { export const spec = { code: BIDDER_CODE, + aliases: BIDDER_ALIAS, gvlid: GVLID, supportedMediaTypes: [ NATIVE ], isBidRequestValid: bid => !!bid.params.mid, @@ -170,7 +172,6 @@ export const spec = { netRevenue: bid.netRevenue === 'net', currency: cur, mediaType: NATIVE, - bidderCode: BIDDER_CODE, native: parseNative(bidResponse) }; } diff --git a/modules/adformOpenRTBBidAdapter.md b/modules/adfBidAdapter.md similarity index 90% rename from modules/adformOpenRTBBidAdapter.md rename to modules/adfBidAdapter.md index 0dd98ad07b8..190aa91ea57 100644 --- a/modules/adformOpenRTBBidAdapter.md +++ b/modules/adfBidAdapter.md @@ -1,13 +1,13 @@ # Overview -Module Name: Adform OpenRTB Adapter +Module Name: Adf Adapter Module Type: Bidder Adapter Maintainer: Scope.FL.Scripts@adform.com # Description Module that connects to Adform demand sources to fetch bids. -Only native format is supported. Using OpenRTB standard. +Only native format is supported. Using OpenRTB standard. Previous adapter name - adformOpenRTB. # Test Parameters ``` @@ -42,7 +42,7 @@ Only native format is supported. Using OpenRTB standard. } }, bids: [{ - bidder: 'adformOpenRTB', + bidder: 'adf', params: { mid: 606169, // required adxDomain: 'adx.adform.net', // optional diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 4fae85e82ad..b9dbae529ba 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -34,6 +34,7 @@ export const spec = { const payload = { slots: slots, parameters: commonParams, + vastContentAsUrl: true, user: { ext: { eids: getEids(validBidRequests), @@ -95,7 +96,7 @@ function adResponse(bid, ad) { const bidResponse = getbaseAdResponse({ requestId: bid.bidId, - mediaType: getMediaType(markup), + mediaType: ad.extension.mediaType, cpm: Number(price.amount), currency: price.currency, width: Number(ad.width), @@ -110,7 +111,11 @@ function adResponse(bid, ad) { }); if (bidResponse.mediaType === VIDEO) { - bidResponse.vastXml = markup; + if (ad.cachedBodyUrl) { + bidResponse.vastUrl = ad.cachedBodyUrl + } else { + bidResponse.vastXml = markup; + } } else { const counter = ad.impressionCounter ? "" : ''; bidResponse.ad = markup + counter; @@ -172,11 +177,6 @@ function isAdheseAd(ad) { return !ad.origin || ad.origin === 'JERLICIA'; } -function getMediaType(markup) { - const isVideo = markup.trim().toLowerCase().match(/<\?xml| { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn' + code: 'adkernelAdn', + gvlid: GVLID }); export default analyticsAdapter; @@ -390,3 +395,19 @@ function getLocationAndReferrer(win) { loc: win.location }; } + +function initPrivacy(template, requests) { + let consent = requests[0].gdprConsent; + if (consent && consent.gdprApplies) { + template.user.gdpr = ~~consent.gdprApplies; + } + if (consent && consent.consentString) { + template.user.gdpr_consent = consent.consentString; + } + if (requests[0].uspConsent) { + template.user.us_privacy = requests[0].uspConsent; + } + if (config.getConfig('coppa')) { + template.user.coppa = 1; + } +} diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 0a0317e1f59..c838d93b8f1 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,11 +1,13 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com'; const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; const DEFAULT_APIS = [1, 2]; +const GVLID = 14; function isRtbDebugEnabled(refInfo) { return refInfo.referer.indexOf('adk_debug=true') !== -1; @@ -67,6 +69,9 @@ function buildRequestParams(tags, bidderRequest) { if (uspConsent) { utils.deepSetValue(req, 'user.us_privacy', uspConsent); } + if (config.getConfig('coppa')) { + utils.deepSetValue(req, 'user.coppa', 1); + } return req; } @@ -110,6 +115,7 @@ function buildBid(tag) { export const spec = { code: 'adkernelAdn', + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['engagesimply'], diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 03fa5c2b2d9..3e795d176d2 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -22,6 +22,7 @@ const SYNC_TYPES = Object.freeze({ 1: 'iframe', 2: 'image' }); +const GVLID = 14; const NATIVE_MODEL = [ {name: 'title', assetType: 'title'}, @@ -50,9 +51,9 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { * Adapter for requesting bids from AdKernel white-label display platform */ export const spec = { - code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm'], + gvlid: GVLID, + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm', 'engageadx'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -320,7 +321,7 @@ function getAllowedSyncMethod(bidderCode) { */ function buildRtbRequest(imps, bidderRequest, schain) { let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - + let coppa = config.getConfig('coppa'); let req = { 'id': auctionId, 'imp': imps, @@ -349,6 +350,9 @@ function buildRtbRequest(imps, bidderRequest, schain) { if (uspConsent) { utils.deepSetValue(req, 'regs.ext.us_privacy', uspConsent); } + if (coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { utils.deepSetValue(req, 'ext.adk_usersync', syncMethod); diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js new file mode 100644 index 00000000000..3e92ae34004 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.js @@ -0,0 +1,288 @@ +/** + * This module provides [Adloox]{@link https://www.adloox.com/} Analytics + * The module will inject Adloox's verification JS tag alongside slot at bidWin + * @module modules/adlooxAnalyticsAdapter + */ + +import adapterManager from '../src/adapterManager.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { AUCTION_COMPLETED } from '../src/auction.js'; +import { EVENTS } from '../src/constants.json'; +import find from 'core-js-pure/features/array/find.js'; +import * as utils from '../src/utils.js'; + +const MODULE = 'adlooxAnalyticsAdapter'; + +const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js'; + +const ADLOOX_VENDOR_ID = 93; + +const ADLOOX_MEDIATYPE = { + DISPLAY: 2, + VIDEO: 6 +}; + +const MACRO = {}; +MACRO['client'] = function(b, c) { + return c.client; +}; +MACRO['clientid'] = function(b, c) { + return c.clientid; +}; +MACRO['tagid'] = function(b, c) { + return c.tagid; +}; +MACRO['platformid'] = function(b, c) { + return c.platformid; +}; +MACRO['targetelt'] = function(b, c) { + return c.toselector(b); +}; +MACRO['creatype'] = function(b, c) { + return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; +}; +MACRO['pbAdSlot'] = function(b, c) { + const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); + return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; +}; + +const PARAMS_DEFAULT = { + 'id1': function(b) { return b.adUnitCode }, + 'id2': '%%pbAdSlot%%', + 'id3': function(b) { return b.bidder }, + 'id4': function(b) { return b.adId }, + 'id5': function(b) { return b.dealId }, + 'id6': function(b) { return b.creativeId }, + 'id7': function(b) { return b.size }, + 'id11': '$ADLOOX_WEBSITE' +}; + +const NOOP = function() {}; + +let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (!analyticsAdapter[`handle_${eventType}`]) return; + + utils.logInfo(MODULE, 'track', eventType, args); + + analyticsAdapter[`handle_${eventType}`](args); + } +}); + +analyticsAdapter.context = null; + +analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; +analyticsAdapter.enableAnalytics = function(config) { + analyticsAdapter.context = null; + + utils.logInfo(MODULE, 'config', config); + + if (!utils.isPlainObject(config.options)) { + utils.logError(MODULE, 'missing options'); + return; + } + if (!(config.options.js === undefined || utils.isStr(config.options.js))) { + utils.logError(MODULE, 'invalid js options value'); + return; + } + if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { + utils.logError(MODULE, 'invalid toselector options value'); + return; + } + if (!utils.isStr(config.options.client)) { + utils.logError(MODULE, 'invalid client options value'); + return; + } + if (!utils.isNumber(config.options.clientid)) { + utils.logError(MODULE, 'invalid clientid options value'); + return; + } + if (!utils.isNumber(config.options.tagid)) { + utils.logError(MODULE, 'invalid tagid options value'); + return; + } + if (!utils.isNumber(config.options.platformid)) { + utils.logError(MODULE, 'invalid platformid options value'); + return; + } + if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { + utils.logError(MODULE, 'invalid params options value'); + return; + } + + analyticsAdapter.context = { + js: config.options.js || URL_JS, + toselector: config.options.toselector || function(bid) { + let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; + // https://mathiasbynens.be/notes/css-escapes + code = code.replace(/^\d/, '\\3$& '); + return `#${code}` + }, + client: config.options.client, + clientid: config.options.clientid, + tagid: config.options.tagid, + platformid: config.options.platformid, + params: [] + }; + + config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); + Object + .keys(config.options.params) + .forEach(k => { + if (!Array.isArray(config.options.params[k])) { + config.options.params[k] = [ config.options.params[k] ]; + } + config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ])); + }); + + Object.keys(COMMAND_QUEUE).forEach(commandProcess); + + analyticsAdapter.originEnableAnalytics(config); +} + +analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; +analyticsAdapter.disableAnalytics = function() { + analyticsAdapter.context = null; + + analyticsAdapter.originDisableAnalytics(); +} + +analyticsAdapter.url = function(url, args, bid) { + // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻ + function a2qs(a) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + } + + const args = []; + let n = a.length; + while (n-- > 0) { + if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) { + args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : '')); + } + } + + return args.join('&'); + } + + const macros = (str) => { + return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match); + }; + + url = macros(url); + args = args || []; + + let n = args.length; + while (n-- > 0) { + if (utils.isFn(args[n][1])) { + try { + args[n][1] = args[n][1](bid); + } catch (_) { + utils.logError(MODULE, 'macro', args[n][0], _.message); + args[n][1] = `ERROR: ${_.message}`; + } + } + if (utils.isStr(args[n][1])) { + args[n][1] = macros(args[n][1]); + } + } + + return url + a2qs(args); +} + +analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { + if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; + analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + + utils.logMessage(MODULE, 'preloading verification JS'); + + const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + + const link = document.createElement('link'); + link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + utils.insertElement(link); +} + +analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { + const sl = analyticsAdapter.context.toselector(bid); + let el; + try { + el = document.querySelector(sl); + } catch (_) { } + if (!el) { + utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); + return; + } + + utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); + + const params = analyticsAdapter.context.params.concat([ + [ 'tagid', '%%tagid%%' ], + [ 'platform', '%%platformid%%' ], + [ 'fwtype', 4 ], + [ 'targetelt', '%%targetelt%%' ], + [ 'creatype', '%%creatype%%' ] + ]); + + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: analyticsAdapter, + code: 'adloox', + gvlid: ADLOOX_VENDOR_ID +}); + +export default analyticsAdapter; + +// src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻ +const COMMAND_QUEUE = {}; +export const COMMAND = { + CONFIG: 'config', + URL: 'url', + TRACK: 'track' +}; +export function command(cmd, data, callback0) { + const cid = utils.getUniqueIdentifierStr(); + const callback = function() { + delete COMMAND_QUEUE[cid]; + if (callback0) callback0.apply(null, arguments); + }; + COMMAND_QUEUE[cid] = { cmd, data, callback }; + if (analyticsAdapter.context) commandProcess(cid); +} +function commandProcess(cid) { + const { cmd, data, callback } = COMMAND_QUEUE[cid]; + + utils.logInfo(MODULE, 'command', cmd, data); + + switch (cmd) { + case COMMAND.CONFIG: + const response = { + client: analyticsAdapter.context.client, + clientid: analyticsAdapter.context.clientid, + tagid: analyticsAdapter.context.tagid, + platformid: analyticsAdapter.context.platformid + }; + callback(response); + break; + case COMMAND.URL: + if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10 + callback(analyticsAdapter.url(data.url, data.args, data.bid)); + break; + case COMMAND.TRACK: + analyticsAdapter.track(data); + callback(); // drain queue + break; + default: + utils.logWarn(MODULE, 'command unknown', cmd); + // do not callback as arguments are unknown and to aid debugging + } +} diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md new file mode 100644 index 00000000000..0ca67f937f6 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.md @@ -0,0 +1,146 @@ +# Overview + + Module Name: Adloox Analytics Adapter + Module Type: Analytics Adapter + Maintainer: technique@adloox.com + +# Description + +Analytics adapter for adloox.com. Contact adops@adloox.com for information. + +This module can be used to track: + + * Display + * Native + * Video (see below for further instructions) + +The adapter adds an HTML ` `; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 6dade7d01f0..8a8cd25479f 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -19,10 +19,11 @@ export const ID5_STORAGE_NAME = 'id5id'; export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; const ABTEST_RESOLUTION = 10000; +const LOG_PREFIX = 'User ID - ID5 submodule: '; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use -const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st' ]; +const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; const storage = getStorageManager(GVLID, MODULE_NAME); @@ -63,14 +64,15 @@ export const id5IdSubmodule = { const controlGroup = isInControlGroup(universalUid, abConfig.controlGroupPct); if (abConfig.enabled === true && typeof controlGroup === 'undefined') { // A/B Testing is enabled, but configured improperly, so skip A/B testing - utils.logError('User ID - ID5 submodule: A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); + utils.logError(LOG_PREFIX + 'A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); } else if (abConfig.enabled === true && controlGroup === true) { // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); - universalUid = linkType = 0; + utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); + universalUid = ''; + linkType = 0; } else if (abConfig.enabled === true) { // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); + utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); } let responseObj = { @@ -86,6 +88,8 @@ export const id5IdSubmodule = { utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', (typeof controlGroup === 'undefined' ? false : controlGroup)); } + utils.logInfo(LOG_PREFIX + 'Decoded ID', responseObj); + return responseObj; }, @@ -104,24 +108,38 @@ export const id5IdSubmodule = { const url = `https://id5-sync.com/g/v2/${config.params.partner}.json`; const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; + const usp = uspDataHandler.getConsentData(); const referer = getRefererInfo(); const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { - 'gdpr': hasGdpr, - 'gdpr_consent': hasGdpr ? consentData.consentString : '', 'partner': config.params.partner, + 'gdpr': hasGdpr, 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', - 'pd': config.params.pd || '', - 'provider': config.params.provider || '', 'rf': referer.referer, - 's': signature, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, - 'us_privacy': uspDataHandler.getConsentData() || '', 'v': '$prebid.version$' }; + // pass in optional data, but only if populated + if (hasGdpr && typeof consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) { + data.gdpr_consent = consentData.consentString; + } + if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) { + data.us_privacy = usp; + } + if (typeof signature !== 'undefined' && !utils.isEmptyStr(signature)) { + data.s = signature; + } + if (typeof config.params.pd !== 'undefined' && !utils.isEmptyStr(config.params.pd)) { + data.pd = config.params.pd; + } + if (typeof config.params.provider !== 'undefined' && !utils.isEmptyStr(config.params.provider)) { + data.provider = config.params.provider; + } + + // pass in feature flags, if applicable if (getAbTestingConfig(config).enabled === true) { utils.deepSetValue(data, 'features.ab', 1); } @@ -133,7 +151,10 @@ export const id5IdSubmodule = { if (response) { try { responseObj = JSON.parse(response); + utils.logInfo(LOG_PREFIX + 'response received from the server', responseObj); + resetNb(config.params.partner); + if (responseObj.privacy) { storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(responseObj.privacy), NB_EXP_DAYS); } @@ -144,16 +165,17 @@ export const id5IdSubmodule = { removeLegacyCookies(config.params.partner); } } catch (error) { - utils.logError(error); + utils.logError(LOG_PREFIX + error); } } callback(responseObj); }, error: error => { - utils.logError(`User ID - ID5 submodule getId fetch encountered an error`, error); + utils.logError(LOG_PREFIX + 'getId fetch encountered an error', error); callback(); } }; + utils.logInfo(LOG_PREFIX + 'requesting an ID from the server', data); ajax(url, callbacks, JSON.stringify(data), { method: 'POST', withCredentials: true }); }; return {callback: resp}; @@ -171,30 +193,34 @@ export const id5IdSubmodule = { * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ extendId(config, consentData, cacheIdObj) { + hasRequiredConfig(config); + const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); + + utils.logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); return cacheIdObj; } }; function hasRequiredConfig(config) { if (!config || !config.params || !config.params.partner || typeof config.params.partner !== 'number') { - utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + utils.logError(LOG_PREFIX + 'partner required to be defined as a number'); return false; } if (!config.storage || !config.storage.type || !config.storage.name) { - utils.logError(`User ID - ID5 submodule requires storage to be set`); + utils.logError(LOG_PREFIX + 'storage required to be set'); return false; } - // TODO: in a future release, return false if storage type or name are not set as required + // in a future release, we may return false if storage type or name are not set as required if (config.storage.type !== LOCAL_STORAGE) { - utils.logWarn(`User ID - ID5 submodule recommends storage type to be '${LOCAL_STORAGE}'. In a future release this will become a strict requirement`); + utils.logWarn(LOG_PREFIX + `storage type recommended to be '${LOCAL_STORAGE}'. In a future release this may become a strict requirement`); } - // TODO: in a future release, return false if storage type or name are not set as required + // in a future release, we may return false if storage type or name are not set as required if (config.storage.name !== ID5_STORAGE_NAME) { - utils.logWarn(`User ID - ID5 submodule recommends storage name to be '${ID5_STORAGE_NAME}'. In a future release this will become a strict requirement`); + utils.logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); } return true; @@ -239,11 +265,12 @@ function getLegacyCookieSignature() { * @param {integer} partnerId */ function removeLegacyCookies(partnerId) { + utils.logInfo(LOG_PREFIX + 'removing legacy cookies'); LEGACY_COOKIE_NAMES.forEach(function(cookie) { - storage.setCookie(`${cookie}`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_nb`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_${partnerId}_nb`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_last`, '', expDaysStr(-1)); + storage.setCookie(`${cookie}`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_${partnerId}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_last`, ' ', expDaysStr(-1)); }); } diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 716929db70a..df7b03b4e6e 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -67,11 +67,11 @@ export const identityLinkSubmodule = { setEnvelopeSource(true); callback(JSON.parse(envelope).envelope); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }; @@ -79,7 +79,7 @@ export const identityLinkSubmodule = { } }; // return envelope from third party endpoint -function getEnvelope(url, callback) { +function getEnvelope(url, callback, configParams) { const callbacks = { success: response => { let responseObj; @@ -98,7 +98,7 @@ function getEnvelope(url, callback) { } }; - if (!storage.getCookie('_lr_retry_request')) { + if (!configParams.notUse3P && !storage.getCookie('_lr_retry_request')) { setRetryCookie(); utils.logInfo('identityLink: A 3P retrieval is attempted!'); setEnvelopeSource(false); diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js new file mode 100644 index 00000000000..b649b5a8a73 --- /dev/null +++ b/modules/impactifyBidAdapter.js @@ -0,0 +1,260 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; +const GETCONFIG = config.getConfig; + +const getDeviceType = () => { + // OpenRTB Device type + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; + } + return 2; +} + +const createOpenRtbRequest = (validBidRequests, bidderRequest) => { + // Create request and set imp bids inside + let request = { + id: bidderRequest.auctionId, + validBidRequests, + cur: [DEFAULT_CURRENCY], + imp: [] + }; + + // Force impactify debugging parameter + if (window.localStorage.getItem('_im_db_bidder') == 3) { + request.test = 3; + } + + // Set device/user/site + if (!request.device) request.device = {}; + if (!request.site) request.site = {}; + request.device = { + w: window.innerWidth, + h: window.innerHeight, + devicetype: getDeviceType(), + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + }; + request.site = {page: bidderRequest.refererInfo.referer}; + + // Handle privacy settings for GDPR/CCPA/COPPA + if (bidderRequest.gdprConsent) { + let gdprApplies = 0; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; + } + + if (GETCONFIG('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set buyer uid + utils.deepSetValue(request, 'user.buyeruid', utils.generateUUID()); + + // Create imps with bids + validBidRequests.forEach((bid) => { + let imp = { + id: bid.bidId, + bidfloor: bid.params.bidfloor ? bid.params.bidfloor : 0, + ext: { + impactify: { + appId: bid.params.appId, + format: bid.params.format, + style: bid.params.style + }, + }, + video: { + playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], + context: 'outstream', + mimes: ['video/mp4'], + }, + }; + if (bid.params.container) { + imp.ext.impactify.container = bid.params.container; + } + request.imp.push(imp); + }); + + return request; +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: ['video'], + aliases: BIDDER_ALIAS, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!bid.params.appId || typeof bid.params.appId != 'string' || !bid.params.format || typeof bid.params.format != 'string' || !bid.params.style || typeof bid.params.style != 'string') { + return false; + } + if (bid.params.format != 'screen' && bid.params.format != 'display') { + return false; + } + if (bid.params.style != 'inline' && bid.params.style != 'impact' && bid.params.style != 'static') { + return false; + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the bidding request + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // Create a clean openRTB request + let request = createOpenRtbRequest(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: ORIGIN + AUCTIONURI, + data: JSON.stringify(request), + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + if (!serverBody.seatbid || !serverBody.seatbid.length) { + return []; + } + + serverBody.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + bidResponses = [ + ...bidResponses, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((bid) => ({ + id: bid.id, + requestId: bid.impid, + cpm: bid.price, + currency: serverBody.cur, + netRevenue: true, + ad: bid.adm, + width: bid.w || 0, + height: bid.h || 0, + ttl: 300, + creativeId: bid.crid || 0, + hash: bid.hash, + expiry: bid.expiry + })), + ]; + } + }); + + return bidResponses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + if (!syncOptions.iframeEnabled) { + return []; + } + + let params = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + if (document.location.search.match(/pbs_debug=true/)) params += `&pbs_debug=true`; + + return [{ + type: 'iframe', + url: ORIGIN + COOKIESYNCURI + params + }]; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + ajax(`${LOGGER_URI}/log/bidder/won`, null, JSON.stringify(bid), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function(data) { + ajax(`${LOGGER_URI}/log/bidder/timeout`, null, JSON.stringify(data[0]), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + } +}; +registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md new file mode 100644 index 00000000000..3de9a8cfb84 --- /dev/null +++ b/modules/impactifyBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Impactify Bidder Adapter +Module Type: Bidder Adapter +Maintainer: thomas.destefano@impactify.io +``` + +# Description + +Module that connects to the Impactify solution. +The impactify bidder need 3 parameters: + - appId : This is your unique publisher identifier + - format : This is the ad format needed, can be : screen or display + - style : This is the ad style needed, can be : inline, impact or static + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot-div-id', // This is your slot div id + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'screen', + style: 'inline' + } + }] + }]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0432adf2b2d..dc0911ff5da 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.2.0', + version: '7.3.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -126,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index 7583985b23c..e1edd935587 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -40,7 +40,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getConfig('fpd'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), prebidVersion: '$prebid.version$' }; diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index c22a6dbc7aa..98732da95d5 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -8,32 +8,111 @@ import * as utils from '../src/utils.js' import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; + +const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; +export const FIRST_PARTY_KEY = '_iiq_fdata'; + +export const storage = getStorageManager(undefined, MODULE_NAME); const NOT_AVAILABLE = 'NA'; /** - * Verify the id is valid - Id value or Not Found (ignore not available response) - * @param id - * @returns {boolean|*|boolean} + * Verify the response is valid - Id value or Not Found (ignore not available response) + * @param response + * @param respJson - parsed json response + * @returns {boolean} */ -function isValidId(id) { - return id && id != '' && id != NOT_AVAILABLE && isValidResponse(id); +function isValidResponse(response, respJson) { + if (!response || response == '' || response === NOT_AVAILABLE) { + // Empty or NA response + return false; + } else if (respJson && (respJson.RESULT === NOT_AVAILABLE || respJson.data == '' || respJson.data === NOT_AVAILABLE)) { + // Response type is json with value NA + return false; + } else { return true; } } /** - * Ignore not available response JSON - * @param obj + * Verify the response json is valid + * @param respJson - parsed json response * @returns {boolean} */ -function isValidResponse(obj) { +function isValidResponseJson(respJson) { + if (respJson && 'data' in respJson) { + return true; + } else { return false; } +} + +/** + * Generate standard UUID string + * @return {string} + */ +function generateGUID() { + let d = new Date().getTime(); + const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + return guid; +} + +/** + * Read Intent IQ data from cookie or local storage + * @param key + * @return {string} + */ +export function readData(key) { try { - obj = JSON.parse(obj); - return obj && obj['RESULT'] != NOT_AVAILABLE; + if (storage.hasLocalStorage()) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } catch (error) { + utils.logError(error); + } +} + +/** + * Store Intent IQ data in either cookie or local storage + * expiration date: 365 days + * @param key + * @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore + */ +function storeData(key, value) { + try { + utils.logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); + + if (value) { + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(key, value); + } + const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); + if (storage.cookiesAreEnabled()) { + storage.setCookie(key, value, expiresStr, 'LAX'); + } + } } catch (error) { utils.logError(error); - return true; + } +} + +/** + * Parse json if possible, else return null + * @param data + * @param {object|null} + */ +function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + utils.logError(err); + return null; } } @@ -51,7 +130,7 @@ export const intentIqIdSubmodule = { * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return isValidId(value) ? { 'intentIqId': value } : undefined; + return isValidResponse(value, undefined) ? { 'intentIqId': value } : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -66,22 +145,42 @@ export const intentIqIdSubmodule = { return; } + // Read Intent IQ 1st party id or generate it if none exists + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); + if (!firstPartyData || !firstPartyData.pcid) { + const firstPartyId = generateGUID(); + firstPartyData = { 'pcid': firstPartyId }; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + } + // use protocol relative urls for http or https let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : ''; url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; + if (firstPartyData) { + url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; + url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; + } const resp = function (callback) { const callbacks = { success: response => { - if (isValidId(response)) { - callback(response); + let respJson = tryParse(response); + if (isValidResponse(response, respJson) && isValidResponseJson(respJson)) { + // Store pid field if found in response json + if (firstPartyData && 'pcid' in firstPartyData && 'pid' in respJson) { + firstPartyData = { + 'pcid': firstPartyData.pcid, + 'pid': respJson.pid } + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + } + callback(respJson.data); } else { callback(); } }, error: error => { - utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + utils.logError(MODULE_NAME + ': ID fetch encountered an error', error); callback(); } }; diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js new file mode 100644 index 00000000000..ea45c00903a --- /dev/null +++ b/modules/interactiveOffersBidAdapter.js @@ -0,0 +1,149 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'interactiveOffers'; +const ENDPOINT = 'https://rtb.ioadx.com/bidRequest/?partnerId=4a3bab187a74ac4862920cca864d6eff195ff5e4'; + +const DEFAULT = { + 'OpenRTBBidRequest': {}, + 'OpenRTBBidRequestSite': {}, + 'OpenRTBBidRequestSitePublisher': {}, + 'OpenRTBBidRequestSiteContent': { + language: navigator.language, + }, + 'OpenRTBBidRequestSource': {}, + 'OpenRTBBidRequestDevice': { + ua: navigator.userAgent, + language: navigator.language + }, + 'OpenRTBBidRequestUser': {}, + 'OpenRTBBidRequestImp': {}, + 'OpenRTBBidRequestImpBanner': {}, + 'PrebidBid': { + currency: 'USD', + ttl: 60, + netRevenue: false + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + let ret = true; + if (bid && bid.params) { + if (!utils.isNumber(bid.params.pubid)) { + utils.logWarn('pubid must be a valid numeric ID'); + ret = false; + } + if (bid.params.tmax && !utils.isNumber(bid.params.tmax)) { + utils.logWarn('tmax must be a valid numeric ID'); + ret = false; + } + } else { + utils.logWarn('invalid request'); + ret = false; + } + return ret; + }, + buildRequests: function(validBidRequests, bidderRequest) { + let payload = parseRequestPrebidjsToOpenRTB(bidderRequest); + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + bidderRequest: bidderRequest + }; + }, + + interpretResponse: function(response, request) { + let bidResponses = []; + if (response.body && response.body.length) { + bidResponses = parseResponseOpenRTBToPrebidjs(response.body); + } + return bidResponses; + } +}; + +function parseRequestPrebidjsToOpenRTB(prebidRequest) { + let pageURL = window.location.href; + let domain = window.location.hostname; + let secure = (window.location.protocol == 'https:' ? 1 : 0); + let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); + openRTBRequest.id = prebidRequest.auctionId; + + openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); + openRTBRequest.site.id = domain; + openRTBRequest.site.name = domain; + openRTBRequest.site.domain = domain; + openRTBRequest.site.page = pageURL; + openRTBRequest.site.ref = prebidRequest.refererInfo.referer; + + openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); + openRTBRequest.site.publisher.id = 0; + openRTBRequest.site.publisher.name = config.getConfig('publisherDomain'); + openRTBRequest.site.publisher.domain = domain; + openRTBRequest.site.publisher.domain = domain; + + openRTBRequest.site.content = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSiteContent'])); + + openRTBRequest.source = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSource'])); + openRTBRequest.source.fd = 0; + openRTBRequest.source.tid = prebidRequest.auctionId; + openRTBRequest.source.pchain = ''; + + openRTBRequest.device = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestDevice'])); + + openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); + + openRTBRequest.imp = []; + prebidRequest.bids.forEach(function(bid, impId) { + impId++; + let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); + imp.id = impId; + imp.secure = secure; + imp.tagid = bid.bidId; + + openRTBRequest.site.publisher.id = openRTBRequest.site.publisher.id || bid.params.pubid; + openRTBRequest.tmax = openRTBRequest.tmax || bid.params.tmax || 0; + + Object.keys(bid.mediaTypes).forEach(function(mediaType) { + if (mediaType == 'banner') { + imp.banner = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImpBanner'])); + imp.banner.w = 0; + imp.banner.h = 0; + imp.banner.format = []; + bid.mediaTypes[mediaType].sizes.forEach(function(adSize) { + if (!imp.banner.w) { + imp.banner.w = adSize[0]; + imp.banner.h = adSize[1]; + } + imp.banner.format.push({w: adSize[0], h: adSize[1]}); + }); + } + }); + openRTBRequest.imp.push(imp); + }); + return openRTBRequest; +} +function parseResponseOpenRTBToPrebidjs(openRTBResponse) { + let prebidResponse = []; + openRTBResponse.forEach(function(response) { + response.seatbid.forEach(function(seatbid) { + seatbid.bid.forEach(function(bid) { + let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); + prebid.requestId = bid.ext.tagid; + prebid.ad = bid.adm; + prebid.creativeId = bid.crid; + prebid.cpm = bid.price; + prebidResponse.push(prebid); + }); + }); + }); + return prebidResponse; +} + +registerBidder(spec); diff --git a/modules/interactiveOffersBidAdapter.md b/modules/interactiveOffersBidAdapter.md index 7eb440c8216..35be59862ef 100644 --- a/modules/interactiveOffersBidAdapter.md +++ b/modules/interactiveOffersBidAdapter.md @@ -1,14 +1,14 @@ # Overview - + ``` Module Name: interactiveOffers Bidder Adapter Module Type: Bidder Adapter -Maintainer: devteam@interactiveoffers.com +Maintainer: faria@interactiveoffers.com ``` # Description -Module that connects to interactiveOffers demand sources. Param pubId is required. +Module that connects to interactiveOffers demand sources. Param pubid is required. # Test Parameters ``` @@ -24,11 +24,11 @@ Module that connects to interactiveOffers demand sources. Param pubId is require { bidder: "interactiveOffers", params: { - pubId: '10', - tmax: 5000 + pubid: 10, + tmax: 250 } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 50ed7d5f57c..d4d26b1e017 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', @@ -8,9 +8,10 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 4, + PREBID_VERSION: 5, METHOD: 'GET', - INVIBES_VENDOR_ID: 436 + INVIBES_VENDOR_ID: 436, + USERID_PROVIDERS: ['pubcid', 'pubProvidedId'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); @@ -54,6 +55,7 @@ registerBidder(spec); const topWin = getTopMostWindow(); let invibes = topWin.invibes = topWin.invibes || {}; invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false]; +invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; let _customUserSync; function isBidRequestValid(bid) { @@ -77,7 +79,7 @@ function isBidRequestValid(bid) { function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; - let _loginId, _customEndpoint; + let _loginId, _customEndpoint, _userId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { @@ -86,6 +88,7 @@ function buildRequest(bidRequests, bidderRequest) { _loginId = _loginId || bidRequest.params.loginId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; + _userId = _userId || bidRequest.userId; }); invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); @@ -95,19 +98,23 @@ function buildRequest(bidRequests, bidderRequest) { let lid = initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); - + let userIdModel = getUserIds(_userId); + let bidParamsJson = { + placementIds: _placementIds, + loginId: _loginId, + auctionStartTime: _ivAuctionStart, + bidVersion: CONSTANTS.PREBID_VERSION + }; + if (userIdModel) { + bidParamsJson.userId = userIdModel; + } let data = { location: getDocumentLocation(topWin), videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), - bidParamsJson: JSON.stringify({ - placementIds: _placementIds, - loginId: _loginId, - auctionStartTime: _ivAuctionStart, - bidVersion: CONSTANTS.PREBID_VERSION - }), + bidParamsJson: JSON.stringify(bidParamsJson), capCounts: getCappedCampaignsAsString(), vId: invibes.visitId, @@ -118,6 +125,7 @@ function buildRequest(bidRequests, bidderRequest) { kw: keywords, purposes: invibes.purposes.toString(), + li: invibes.legitimateInterests.toString(), tc: invibes.gdpr_consent }; @@ -140,7 +148,7 @@ function buildRequest(bidRequests, bidderRequest) { method: CONSTANTS.METHOD, url: _customEndpoint || CONSTANTS.BID_ENDPOINT, data: data, - options: { withCredentials: true }, + options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } bidRequests: bidRequests }; @@ -228,9 +236,26 @@ function getDocumentLocation(topWin) { return topWin.location.href.substring(0, 300).split(/[?#]/)[0]; } +function getUserIds(bidUserId) { + let userId; + if (bidUserId) { + CONSTANTS.USERID_PROVIDERS.forEach(provider => { + if (bidUserId[provider]) { + userId = userId || {}; + userId[provider] = bidUserId[provider]; + } + }); + } + + return userId; +} + function parseQueryStringParams() { let params = {}; - try { params = JSON.parse(localStorage.ivbs); } catch (e) { } + try { + params = JSON.parse(localStorage.ivbs); + } catch (e) { + } let re = /[\\?&]([^=]+)=([^\\?&#]+)/g; let m; while ((m = re.exec(window.location.href)) != null) { @@ -257,9 +282,12 @@ function getTopMostWindow() { try { while (top !== res) { - if (res.parent.location.href.length) { res = res.parent; } + if (res.parent.location.href.length) { + res = res.parent; + } } - } catch (e) { } + } catch (e) { + } return res; } @@ -277,7 +305,9 @@ function renderCreative(bidModel) { function getCappedCampaignsAsString() { const key = 'ivvcap'; - if (!invibes.optIn || !invibes.purposes[0]) { return ''; } + if (!invibes.optIn || !invibes.purposes[0]) { + return ''; + } let loadData = function () { try { @@ -311,24 +341,31 @@ function getCappedCampaignsAsString() { clearExpired(); let data = loadData(); return Object.keys(data) - .filter(function (k) { return data.hasOwnProperty(k); }) + .filter(function (k) { + return data.hasOwnProperty(k); + }) .sort() - .map(function (k) { return [k, data[k][0]]; }); + .map(function (k) { + return [k, data[k][0]]; + }); }; return getCappedCampaigns() - .map(function (record) { return record.join('='); }) + .map(function (record) { + return record.join('='); + }) .join(','); } -const noop = function () { }; +const noop = function () { +}; function initLogger() { if (storage.hasLocalStorage() && localStorage.InvibesDEBUG) { return window.console; } - return { info: noop, error: noop, log: noop, warn: noop, debug: noop }; + return {info: noop, error: noop, log: noop, warn: noop, debug: noop}; } function buildSyncUrl() { @@ -358,39 +395,37 @@ function readGdprConsent(gdprConsent) { for (index = 0; index < invibes.purposes; ++index) { invibes.purposes[index] = true; } + + for (index = 0; index < invibes.legitimateInterests.length; ++index) { + invibes.legitimateInterests[index] = true; + } return 2; } let purposeConsents = getPurposeConsents(gdprConsent.vendorData); - if (purposeConsents == null) { return 0; } + if (purposeConsents == null) { + return 0; + } let purposesLength = getPurposeConsentsCounter(gdprConsent.vendorData); - if (purposeConsents instanceof Array) { - for (let i = 0; i < purposesLength && i < purposeConsents.length; i++) { - invibes.purposes[i] = !((purposeConsents[i] === false || purposeConsents[i] === 'false' || purposeConsents[i] == null)); - } - } else if (typeof purposeConsents === 'object' && purposeConsents !== null) { - let i = 0; - for (let prop in purposeConsents) { - if (i === purposesLength) { - break; - } - - if (purposeConsents.hasOwnProperty(prop)) { - invibes.purposes[i] = !((purposeConsents[prop] === false || purposeConsents[prop] === 'false' || purposeConsents[prop] == null)); - i++; - } - } - } else { + if (!tryCopyValueToArray(purposeConsents, invibes.purposes, purposesLength)) { return 0; } + let legitimateInterests = getLegitimateInterests(gdprConsent.vendorData); + tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, 10); + let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10); let vendorConsents = getVendorConsents(gdprConsent.vendorData); - if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { return 4; } + let vendorHasLegitimateInterest = getVendorLegitimateInterest(gdprConsent.vendorData)[invibesVendorId] === true; + if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { + return 4; + } - if (vendorConsents[invibesVendorId] === false) { return 0; } + if (vendorConsents[invibesVendorId] === false && vendorHasLegitimateInterest === false) { + return 0; + } return 2; } @@ -398,6 +433,31 @@ function readGdprConsent(gdprConsent) { return 0; } +function tryCopyValueToArray(value, target, length) { + if (value instanceof Array) { + for (let i = 0; i < length && i < value.length; i++) { + target[i] = !((value[i] === false || value[i] === 'false' || value[i] == null)); + } + return true; + } + if (typeof value === 'object' && value !== null) { + let i = 0; + for (let prop in value) { + if (i === length) { + break; + } + + if (value.hasOwnProperty(prop)) { + target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + i++; + } + } + return true; + } + + return false; +} + function getPurposeConsentsCounter(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { return 10; @@ -418,9 +478,19 @@ function getPurposeConsents(vendorData) { return null; } +function getLegitimateInterests(vendorData) { + if (vendorData.purpose && vendorData.purpose.legitimateInterests) { + return vendorData.purpose.legitimateInterests; + } + + return null; +} + function getVendorConsentData(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { - if (vendorData.tcString != null) { return vendorData.tcString; } + if (vendorData.tcString != null) { + return vendorData.tcString; + } } return vendorData.consentData; }; @@ -437,13 +507,23 @@ function getVendorConsents(vendorData) { return null; } +function getVendorLegitimateInterest(vendorData) { + if (vendorData.vendor && vendorData.vendor.legitimateInterests) { + return vendorData.vendor.legitimateInterests; + } + + return {}; +} + const ivLogger = initLogger(); /// Local domain cookie management ===================== invibes.Uid = { generate: function () { let maxRand = parseInt('zzzzzz', 36) - let mkRand = function () { return Math.floor(Math.random() * maxRand).toString(36); }; + let mkRand = function () { + return Math.floor(Math.random() * maxRand).toString(36); + }; let rand1 = mkRand(); let rand2 = mkRand(); return rand1 + rand2; @@ -451,9 +531,13 @@ invibes.Uid = { }; invibes.getCookie = function (name) { - if (!storage.cookiesAreEnabled()) { return; } + if (!storage.cookiesAreEnabled()) { + return; + } - if (!invibes.optIn || !invibes.purposes[0]) { return; } + if (!invibes.optIn || !invibes.purposes[0]) { + return; + } return storage.getCookie(name); }; @@ -465,7 +549,8 @@ let initDomainId = function (options) { let str = invibes.getCookie(this.cname) || ''; try { return JSON.parse(str); - } catch (e) { } + } catch (e) { + } } }; @@ -546,6 +631,7 @@ let keywords = (function () { } return kw; }()); + // ===================== export function resetInvibes() { diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index ba510e86e7f..5b8531d7a85 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7ddad134451..64a3237e02a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -6,6 +6,8 @@ import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; +const ALIAS_BIDDER_CODE = 'roundel'; +const GLOBAL_VENDOR_ID = 10; const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BANNER_ENDPOINT_VERSION = 7.2; @@ -14,11 +16,14 @@ const CENT_TO_DOLLAR_FACTOR = 100; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; + const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; +const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -33,6 +38,8 @@ function bidToBannerImp(bid) { imp.banner.h = bid.params.size[1]; imp.banner.topframe = utils.inIframe() ? 0 : 1; + _applyFloor(bid, imp, BANNER); + return imp; } @@ -46,7 +53,7 @@ function bidToVideoImp(bid) { const imp = bidToImp(bid); const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - const videoAdUnitWhitelist = [ + const videoAdUnitAllowlist = [ 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', @@ -68,12 +75,14 @@ function bidToVideoImp(bid) { } } - for (let adUnitProperty in videoAdUnitRef) { - if (videoAdUnitWhitelist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { + for (const adUnitProperty in videoAdUnitRef) { + if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; } } + _applyFloor(bid, imp, VIDEO); + return imp; } @@ -92,12 +101,73 @@ function bidToImp(bid) { imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; } - if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { - imp.bidfloor = bid.params.bidFloor; - imp.bidfloorcur = bid.params.bidFloorCur; + return imp; +} + +/** + * Gets priceFloors floors and IX adapter floors, + * Validates and sets the higher one on the impression + * @param {object} bid bid object + * @param {object} imp impression object + * @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES + */ +function _applyFloor(bid, imp, mediaType) { + let adapterFloor = null; + let moduleFloor = null; + + if (bid.params.bidFloor && bid.params.bidFloorCur) { + adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur }; + } + + if (utils.isFn(bid.getFloor)) { + let _mediaType = '*'; + let _size = '*'; + + if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + try { + moduleFloor = bid.getFloor({ + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + utils.logWarn('priceFloors module call getFloor failed, error : ', err); + } } - return imp; + if (adapterFloor && moduleFloor) { + if (adapterFloor.currency !== moduleFloor.currency) { + utils.logWarn('The bid floor currency mismatch between IX params and priceFloors module config'); + return; + } + + if (adapterFloor.floor > moduleFloor.floor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } + return; + } + + if (moduleFloor) { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } else if (adapterFloor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + utils.logInfo('IX Bid Adapter: No floors available, no floors applied'); + } } /** @@ -224,7 +294,8 @@ function getEidInfo(allEids) { 'liveramp.com': 'idl', 'netid.de': 'NETID', 'neustar.biz': 'fabrickId', - 'zeotap.com': 'zeotapIdPlus' + 'zeotap.com': 'zeotapIdPlus', + 'uidapi.com': 'UID2' }; var toSend = []; var seenSources = {}; @@ -270,13 +341,19 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (identityInfo.hasOwnProperty(partnerName)) { let response = identityInfo[partnerName]; if (!response.responsePending && response.data && typeof response.data === 'object' && - Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { userEids.push(response.data); } } } } } + + // If `roundel` alias bidder, only send requests if liveramp ids exist. + if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { + return []; + } + const r = {}; // Since bidderRequestId are the same for different bid request, just use the first one. @@ -416,7 +493,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { msd = impressions[transactionIds[i]].missingCount; } - trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + if (BASE_REQ_SIZE < MAX_REQ_SIZE) { + trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + } else { + utils.logError('ix bidder: Base request size has exceeded maximum request size.'); + } if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { msi = impressions[transactionIds[i]].missingImps.length; @@ -481,6 +562,33 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { return requests; } +/** + * Return an object of user IDs stored by Prebid User ID module + * + * @returns {array} ID providers that are present in userIds + */ +function _getUserIds(bidRequest) { + const userIds = bidRequest.userId || {}; + + const PROVIDERS = [ + 'britepoolid', + 'id5id', + 'lipbid', + 'haloId', + 'criteoId', + 'lotamePanoramaId', + 'merkleId', + 'parrableId', + 'connectid', + 'sharedid', + 'tapadId', + 'quantcastId', + 'pubcid' + ] + + return PROVIDERS.filter(provider => utils.deepAccess(userIds, provider)) +} + /** * Calculates IX diagnostics values and packages them into an object * @@ -498,9 +606,10 @@ function buildIXDiag(validBidRequests) { iu: 0, nu: 0, ou: 0, - allU: 0, + allu: 0, ren: false, - version: '$prebid.version$' + version: '$prebid.version$', + userIds: _getUserIds(validBidRequests[0]) }; // create ad unit map and collect the required diag properties @@ -534,7 +643,7 @@ function buildIXDiag(validBidRequests) { ixdiag.iu++; } - ixdiag.allU++; + ixdiag.allu++; } } @@ -610,23 +719,31 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { } /** - * + * @param {object} bid ValidBidRequest object, used to adjust floor * @param {object} imp Impression object to be modified * @param {array} newSize The new size to be applied * @return {object} newImp Updated impression object */ -function createMissingBannerImp(imp, newSize) { +function createMissingBannerImp(bid, imp, newSize) { const newImp = utils.deepClone(imp); newImp.ext.sid = `${newSize[0]}x${newSize[1]}`; newImp.banner.w = newSize[0]; newImp.banner.h = newSize[1]; + + _applyFloor(bid, newImp, BANNER); + return newImp; } export const spec = { code: BIDDER_CODE, - gvlid: 10, + gvlid: GLOBAL_VENDOR_ID, + aliases: [{ + code: ALIAS_BIDDER_CODE, + gvlid: GLOBAL_VENDOR_ID, + skipPbsAliasing: false + }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -658,7 +775,7 @@ export const spec = { } if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) || - (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { + (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); return false; } @@ -730,13 +847,12 @@ export const spec = { if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { videoImps[validBidRequest.transactionId].ixImps = []; } - videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); } } if (validBidRequest.mediaType === BANNER || - (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || - (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { + (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || + (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { let imp = bidToBannerImp(validBidRequest); if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { @@ -767,7 +883,7 @@ export const spec = { let origImp = missingBannerSizes[transactionId].impression; for (let i = 0; i < missingSizes.length; i++) { - let newImp = createMissingBannerImp(origImp, missingSizes[i]); + let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]); bannerImps[transactionId].missingImps.push(newImp); bannerImps[transactionId].missingCount++; } diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index a29c4ce5fa7..8332e720ae7 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,7 +144,7 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { const jwTargeting = extractPublisherParams(adUnit, fpdFallback); if (!jwTargeting || !Object.keys(jwTargeting).length) { @@ -170,7 +170,7 @@ function supportsInstreamVideo(mediaTypes) { export function extractPublisherParams(adUnit, fallback) { let adUnitTargeting; try { - adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; } catch (e) {} if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index ae09277979a..0723e8cbb6c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,14 +25,14 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. @@ -52,7 +52,7 @@ pbjs.que.push(function() { }); ``` -**Note**: You may also include `jwTargeting` information in the prebid config's `fpd.context.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. ##Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: @@ -117,3 +117,7 @@ To view an example: `http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +#Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 03767efc135..610f4558139 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -3,17 +3,19 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}'; +const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; const SYNC_COUNT = 5; +const GVLID = 972; +const storage = getStorageManager(GVLID, BIDDER_CODE); let sessionId, lastPageUrl, requestCounter; export const spec = { + gvlid: GVLID, code: BIDDER_CODE, isBidRequestValid: function(bid) { if (!bid || !bid.params) { @@ -48,7 +50,7 @@ export const spec = { bidIDs: bidIds, bidSizes: bidSizes, prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(tdid, bidderRequest.uspConsent)); + }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); return Object.assign({}, bidderRequest, { method: 'GET', @@ -85,15 +87,25 @@ export const spec = { } return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { const syncs = []; const seed = spec._generateRandomUuid(); const clientId = spec._getClientId(); + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { + return syncs; + } if (syncOptions.iframeEnabled && seed && clientId) { for (let i = 0; i < SYNC_COUNT; i++) { syncs.push({ type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed).replace('{INDEX}', i) + url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') }); } } @@ -183,7 +195,7 @@ export const spec = { } }, - _getUserIds(tdid, usp) { + _getUserIds(tdid, usp, gdpr) { const crb = spec._getCrb(); const userIds = { kargoID: crb.userId, @@ -192,6 +204,16 @@ export const spec = { optOut: crb.optOut, usp: usp }; + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } + } + } catch (e) { + } if (tdid) { userIds.tdID = tdid; } @@ -203,9 +225,9 @@ export const spec = { return crb.clientId; }, - _getAllMetadata(tdid, usp) { + _getAllMetadata(tdid, usp, gdpr) { return { - userIDs: spec._getUserIds(tdid, usp), + userIDs: spec._getUserIds(tdid, usp, gdpr), krux: spec._getKrux(), pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js new file mode 100644 index 00000000000..cc5b374af95 --- /dev/null +++ b/modules/koblerBidAdapter.js @@ -0,0 +1,231 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {getRefererInfo} from '../src/refererDetection.js'; + +const BIDDER_CODE = 'kobler'; +const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; +const SUPPORTED_CURRENCY = 'USD'; +const DEFAULT_TIMEOUT = 1000; +const TIME_TO_LIVE_IN_SECONDS = 10 * 60; + +export const isBidRequestValid = function (bid) { + return !!(bid && bid.bidId && bid.params && bid.params.placementId); +}; + +export const buildRequests = function (validBidRequests, bidderRequest) { + return { + method: 'POST', + url: BIDDER_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), + options: { + contentType: 'application/json' + } + }; +}; + +export const interpretResponse = function (serverResponse) { + const res = serverResponse.body; + const bids = [] + if (res) { + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + bids.push({ + requestId: b.impid, + cpm: b.price, + currency: res.cur, + width: b.w, + height: b.h, + creativeId: b.crid, + dealId: b.dealid, + netRevenue: true, + ttl: TIME_TO_LIVE_IN_SECONDS, + ad: b.adm, + nurl: b.nurl, + meta: { + advertiserDomains: b.adomain + } + }) + }) + }); + } + return bids; +}; + +export const onBidWon = function (bid) { + const cpm = bid.cpm || 0; + const adServerPrice = utils.deepAccess(bid, 'adserverTargeting.hb_pb', 0); + if (utils.isStr(bid.nurl) && bid.nurl !== '') { + const winNotificationUrl = utils.replaceAuctionPrice(bid.nurl, cpm) + .replace(/\${AD_SERVER_PRICE}/g, adServerPrice); + utils.triggerPixel(winNotificationUrl); + } +}; + +export const onTimeout = function (timeoutDataArray) { + if (utils.isArray(timeoutDataArray)) { + const refererInfo = getRefererInfo(); + const pageUrl = (refererInfo && refererInfo.referer) + ? refererInfo.referer + : window.location.href; + timeoutDataArray.forEach(timeoutData => { + const query = utils.parseQueryStringParameters({ + ad_unit_code: timeoutData.adUnitCode, + auction_id: timeoutData.auctionId, + bid_id: timeoutData.bidId, + timeout: timeoutData.timeout, + placement_id: utils.deepAccess(timeoutData, 'params.0.placementId'), + page_url: pageUrl, + }); + const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; + utils.triggerPixel(timeoutNotificationUrl); + }); + } +}; + +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + const imps = validBidRequests.map(buildOpenRtbImpObject); + const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) + ? bidderRequest.refererInfo.referer + : window.location.href; + + const request = { + id: bidderRequest.auctionId, + at: 1, + tmax: timeout, + cur: [SUPPORTED_CURRENCY], + imp: imps, + device: { + devicetype: getDevice(), + geo: getGeo(validBidRequests[0]) + }, + site: { + page: pageUrl, + }, + test: getTest(validBidRequests[0]) + }; + + return JSON.stringify(request); +} + +function buildOpenRtbImpObject(validBidRequest) { + const sizes = getSizes(validBidRequest); + const mainSize = sizes[0]; + const floorInfo = getFloorInfo(validBidRequest, mainSize); + + return { + id: validBidRequest.bidId, + banner: { + format: buildFormatArray(sizes), + w: mainSize[0], + h: mainSize[1], + ext: { + kobler: { + pos: getPosition(validBidRequest) + } + } + }, + tagid: validBidRequest.params.placementId, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, + pmp: buildPmpObject(validBidRequest) + }; +} + +function getDevice() { + const ws = utils.getWindowSelf(); + const ua = ws.navigator.userAgent; + + if (/(tablet|ipad|playbook|silk|android 3.0|xoom|sch-i800|kindle)|(android(?!.*mobi))/i + .test(ua.toLowerCase())) { + return 5; // tablet + } + if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i + .test(ua.toLowerCase())) { + return 4; // phone + } + return 2; // personal computers +} + +function getGeo(validBidRequest) { + if (validBidRequest.params.zip) { + return { + zip: validBidRequest.params.zip + }; + } + return {}; +} + +function getTest(validBidRequest) { + return validBidRequest.params.test ? 1 : 0; +} + +function getSizes(validBidRequest) { + const sizes = utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes', validBidRequest.sizes); + if (utils.isArray(sizes) && sizes.length > 0) { + return sizes; + } + + return [[0, 0]]; +} + +function buildFormatArray(sizes) { + return sizes.map(size => { + return { + w: size[0], + h: size[1] + }; + }); +} + +function getPosition(validBidRequest) { + return parseInt(validBidRequest.params.position) || 0; +} + +function getFloorInfo(validBidRequest, mainSize) { + if (typeof validBidRequest.getFloor === 'function') { + const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; + return validBidRequest.getFloor({ + currency: SUPPORTED_CURRENCY, + mediaType: BANNER, + size: sizeParam + }); + } else { + return { + currency: SUPPORTED_CURRENCY, + floor: getFloorPrice(validBidRequest) + }; + } +} + +function getFloorPrice(validBidRequest) { + return parseFloat(validBidRequest.params.floorPrice) || 0.0; +} + +function buildPmpObject(validBidRequest) { + if (validBidRequest.params.dealIds) { + return { + deals: validBidRequest.params.dealIds.map(dealId => { + return { + id: dealId + }; + }) + }; + } + return {}; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + onTimeout +}; + +registerBidder(spec); diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md new file mode 100644 index 00000000000..7a7b2388367 --- /dev/null +++ b/modules/koblerBidAdapter.md @@ -0,0 +1,67 @@ +# Overview + +**Module Name**: Kobler Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: bidding-support@kobler.no + +# Description + +Connects to Kobler's demand sources. + +This adapter currently only supports Banner Ads. + +# Parameters + +| Parameter (in `params`) | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | +| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +# Test Parameters +```javascript + const adUnits = [{ + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[320, 250], [300, 250]], + } + }, + bids: [{ + bidder: 'kobler', + params: { + placementId: 'k5H7et3R0' + } + }] + }]; +``` + +In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: +- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. +- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. + +# Test Optional Parameters +```javascript + const adUnits = [{ + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[320, 250], [300, 250]], + } + }, + bids: [{ + bidder: 'kobler', + params: { + placementId: 'k5H7et3R0', + zip: '102 22', + test: true, + floorPrice: 5.0, + position: 1, + dealIds: ['abc328745', 'mxw243253'] + } + }] + }]; +``` diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 926761e5ab2..c7440743d2c 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -84,6 +84,30 @@ function _initConf(refererInfo) { return conf; } +function _setFloor(impObj, bid) { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function') { + [BANNER, VIDEO].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) + } + } + }); + } + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor) + } + + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined); +} + function parseRTBResponse(request, response) { var bidResponses = []; try { @@ -352,11 +376,9 @@ function _getImpressionObject(bid) { secure: window.location.protocol === 'https:' ? 1 : 0, bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY }; - if (params.bidFloor) { impression.bidfloor = params.bidFloor; } - if (bid.hasOwnProperty('mediaTypes')) { for (mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -392,7 +414,7 @@ function _getImpressionObject(bid) { } impression.banner = bObj; } - + _setFloor(impression, bid); return impression.hasOwnProperty(BANNER) || impression.hasOwnProperty(VIDEO) ? impression : undefined; } diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index f87d67aae8e..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -166,15 +166,7 @@ export const liveIntentIdSubmodule = { const result = function(callback) { liveConnect.resolve( response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + callback(response); }, error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index f3e9709cd11..93552638007 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -81,6 +81,8 @@ export const spec = { version: VERSION, gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined, gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined, + coppa: getCoppa(), + usPrivacy: bidderRequest.uspConsent, cookieSupport: !utils.isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], @@ -259,34 +261,10 @@ function getAdblockerRecovered() { } catch (e) {} } -function AddExternalUserId(eids, value, source, atype, rtiPartner) { - if (utils.isStr(value)) { - var eid = { - source, - uids: [{ - id: value, - atype - }] - }; - - if (rtiPartner) { - eid.uids[0] = {ext: {rtiPartner}}; - } - - eids.push(eid); - } -} - function handleEids(bidRequests) { - let eids = []; const bidRequest = bidRequests[0]; - if (bidRequest && bidRequest.userId) { - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); // Also add this to eids - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 1); - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteoId`), 'criteo.com', 1); - } - if (eids.length > 0) { - return {user: {ext: {eids}}}; + if (bidRequest && bidRequest.userIdAsEids) { + return {user: {ext: {eids: bidRequest.userIdAsEids}}}; } return undefined; @@ -333,4 +311,9 @@ function getDeviceHeight() { return window.innerHeight; } +function getCoppa() { + if (typeof config.getConfig('coppa') === 'boolean') { + return config.getConfig('coppa'); + } +} registerBidder(spec); diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 0bf6b886dee..2c919f9c157 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -62,6 +62,7 @@ function newBidRequest(bid, bidderRequest) { prebidJsVersion: '$prebid.version$', referrer: bidderRequest.refererInfo.referer, auctionStartTime: bidderRequest.auctionStart, + eids: bid.userIdAsEids, }; } diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 4f7fd2ae1a0..29b54f77fbb 100644 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -279,8 +279,9 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const siteData = Object.assign({}, bidRequest.params.inventory, fpd.context); + const userData = Object.assign({}, bidRequest.params.visitor, fpd.user); if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { const bidderData = { @@ -301,7 +302,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 1ce2558b8de..bb1763ebb2e 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; function MarsmediaAdapter() { this.code = 'marsmedia'; @@ -16,7 +17,7 @@ function MarsmediaAdapter() { let SUPPORTED_VIDEO_API = [1, 2, 5]; let slotsToBids = {}; let that = this; - let version = '2.3'; + let version = '2.4'; this.isBidRequestValid = function (bid) { return !!(bid.params && bid.params.zoneId); @@ -53,6 +54,7 @@ function MarsmediaAdapter() { if (!(impObj.banner || impObj.video)) { continue; } + impObj.bidfloor = _getFloor(BRs[i]); impObj.ext = frameExt(BRs[i]); impList.push(impObj); } @@ -153,9 +155,31 @@ function MarsmediaAdapter() { } function frameExt(bid) { - return { - bidder: { - zoneId: bid.params['zoneId'] + if ((bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes)) { + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => utils.isArray(size)); + const processedSizes = bidSizes.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); + + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: viewabilityAmountRounded + } + } else { + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: 'na' } } } @@ -180,12 +204,15 @@ function MarsmediaAdapter() { } }; if (BRs[0].schain) { - bid.source = { - 'ext': { - 'schain': BRs[0].schain - } - } + utils.deepSetValue(bid, 'source.ext.schain', BRs[0].schain); + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(bid, 'regs.ext.us_privacy', bidderRequest.uspConsent) } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(bid, 'regs.coppa', config.getConfig('coppa') & 1) + } + return bid; } @@ -241,12 +268,6 @@ function MarsmediaAdapter() { sendbeacon(bid, 20) }; - function sendbeacon(bid, type) { - const bidString = JSON.stringify(bid); - const encodedBuf = window.btoa(bidString); - utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); - } - this.interpretResponse = function (serverResponse) { let responses = serverResponse.body || []; let bids = []; @@ -295,6 +316,126 @@ function MarsmediaAdapter() { return bids; }; + + function sendbeacon(bid, type) { + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); + } + + /** + * Gets bidfloor + * @param {Object} bid + * @returns {Number} floor + */ + function _getFloor (bid) { + const curMediaType = bid.mediaTypes.video ? 'video' : 'banner'; + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = floorInfo.floor; + } + } + + return floor; + } + + function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); + } + + function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; + } + + function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } + } + + function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; + } + + function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + 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); + } + + return 0; + } + + 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; + } } export const spec = new MarsmediaAdapter(); diff --git a/modules/mass.js b/modules/mass.js new file mode 100644 index 00000000000..01135e7ddff --- /dev/null +++ b/modules/mass.js @@ -0,0 +1,184 @@ +/** + * This module adds MASS support to Prebid.js. + */ + +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +const defaultCfg = { + dealIdPattern: /^MASS/i +}; +let cfg; + +export let listenerAdded = false; +export let isEnabled = false; + +const matchedBids = {}; +let renderers; + +init(); +config.getConfig('mass', config => init(config.mass)); + +/** + * Module init. + */ +export function init(userCfg) { + cfg = Object.assign({}, defaultCfg, window.massConfig && window.massConfig.mass, userCfg); + + if (cfg.enabled === false) { + if (isEnabled) { + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + isEnabled = false; + } + } else { + if (!isEnabled) { + getHook('addBidResponse').before(addBidResponseHook); + isEnabled = true; + } + } + + if (isEnabled) { + updateRenderers(); + } +} + +/** + * Update the list of renderers based on current config. + */ +export function updateRenderers() { + renderers = []; + + // official MASS renderer: + if (cfg.dealIdPattern && cfg.renderUrl) { + renderers.push({ + match: isMassBid, + render: useDefaultRender(cfg.renderUrl, 'mass') + }); + } + + // add any custom renderer defined in the config: + (cfg.custom || []).forEach(renderer => { + if (!renderer.match && renderer.dealIdPattern) { + renderer.match = useDefaultMatch(renderer.dealIdPattern); + } + + if (!renderer.render && renderer.renderUrl && renderer.namespace) { + renderer.render = useDefaultRender(renderer.renderUrl, renderer.namespace); + } + + if (renderer.match && renderer.render) { + renderers.push(renderer); + } + }); + + return renderers; +} + +/** + * Before hook for 'addBidResponse'. + */ +export function addBidResponseHook(next, adUnitCode, bid) { + let renderer; + for (let i = 0; i < renderers.length; i++) { + if (renderers[i].match(bid)) { + renderer = renderers[i]; + break; + } + } + + if (renderer) { + const bidRequest = find(this.bidderRequest.bids, bidRequest => + bidRequest.bidId === bid.requestId + ); + + matchedBids[bid.requestId] = { + renderer, + payload: { + bidRequest, + bid, + adm: bid.ad + } + }; + + bid.ad = '`; + break; + } + }); + } + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function getNativeAssets(bid) { + return utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!utils.isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (utils.isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/outbrainBidAdapter.md b/modules/outbrainBidAdapter.md new file mode 100644 index 00000000000..32df22ddad8 --- /dev/null +++ b/modules/outbrainBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: Outbrain Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to Outbrain bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Outbrain adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + } + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + }, + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 8ec215d3cca..91e88d3e4e1 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,10 +8,11 @@ import { getGlobal } from '../src/prebidGlobal.js' import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js' -import { deepSetValue, deepAccess, isFn, mergeDeep } from '../src/utils.js' +import { deepSetValue, deepAccess, isFn, mergeDeep, logError } from '../src/utils.js' import includes from 'core-js-pure/features/array/includes.js' +const MODULE_NAME = 'permutive' -export const storage = getStorageManager() +export const storage = getStorageManager(null, MODULE_NAME) function init (config, userConsent) { return true @@ -60,13 +61,19 @@ function setSegments (reqBidsConfigObj, config) { customFn(bid, data, acEnabled, utils, defaultFn) } else if (defaultFn) { defaultFn(bid, data, acEnabled) - } else { - } }) }) } +function makeSafe (fn) { + try { + fn() + } catch (e) { + logError(e) + } +} + function getCustomBidderFn (config, bidder) { const overwriteFn = deepAccess(config, `params.overwrites.${bidder}`) @@ -110,6 +117,13 @@ function getDefaultBidderFn (bidder) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } + return bid + }, + trustx: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.keywords.p_standard', data.ac) + } + return bid } } @@ -163,8 +177,12 @@ function readSegments (key) { /** @type {RtdSubmodule} */ export const permutiveSubmodule = { - name: 'permutive', - getBidRequestData: initSegments, + name: MODULE_NAME, + getBidRequestData: function (reqBidsConfigObj, callback, customConfig) { + makeSafe(function () { + initSegments(reqBidsConfigObj, callback, customConfig) + }) + }, init: init } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 55bdf6420cf..fe8c34c1b5c 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -33,6 +33,7 @@ The below bidders are currently support by the Permutive RTD module. Please reac | Xandr | `appnexus` | Yes | Yes | | Magnite | `rubicon` | Yes | Yes | | Ozone | `ozone` | No | Yes | +| TrustX | `trustx` | No | Yes | * **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in Permutive. Permutive segments will be sent in the `permutive` key-value. diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 56e4fdfbfef..92a8f5deaa5 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -3,15 +3,40 @@ export const S2S_VENDORS = { 'appnexus': { adapter: 'prebidServer', enabled: true, - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', - syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction' + }, + syncEndpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + }, timeout: 1000 }, 'rubicon': { adapter: 'prebidServer', enabled: true, - endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', + endpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + }, + syncEndpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + }, timeout: 500 + }, + 'openx': { + adapter: 'prebidServer', + enabled: true, + endpoint: { + p1Consent: 'https://prebid.openx.net/openrtb2/auction', + noP1Consent: 'https://prebid.openx.net/openrtb2/auction' + }, + syncEndpoint: { + p1Consent: 'https://prebid.openx.net/cookie_sync', + noP1Consent: 'https://prebid.openx.net/cookie_sync' + }, + timeout: 1000 } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d64dad31b17..38a499c794e 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -89,7 +89,7 @@ config.setDefaults({ * @return {boolean} */ function updateConfigDefaultVendor(option) { - if (option.defaultVendor) { + if (option.defaultVendor && option.enabled !== false) { let vendor = option.defaultVendor; let optionKeys = Object.keys(option); if (S2S_VENDORS[vendor]) { @@ -124,6 +124,17 @@ function validateConfigRequiredProps(option) { } } +// temporary change to modify the s2sConfig for new format used for endpoint URLs; +// could be removed later as part of a major release, if we decide to not support the old format +function formatUrlParams(option) { + ['endpoint', 'syncEndpoint'].forEach((prop) => { + if (utils.isStr(option[prop])) { + let temp = option[prop]; + option[prop] = { p1Consent: temp, noP1Consent: temp }; + } + }); +} + /** * @param {(S2SConfig[]|S2SConfig)} options */ @@ -135,6 +146,7 @@ function setS2sConfig(options) { const activeBidders = []; const optionsValid = normalizedOptions.every((option, i, array) => { + formatUrlParams(options); const updateSuccess = updateConfigDefaultVendor(option); if (updateSuccess !== false) { const valid = validateConfigRequiredProps(option); @@ -188,17 +200,14 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { } if (gdprConsent) { - // only populate gdpr field if we know CMP returned consent information (ie didn't timeout or have an error) - if (typeof gdprConsent.consentString !== 'undefined') { - payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; - } + payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; // attempt to populate gdpr_consent if we know gdprApplies or it may apply if (gdprConsent.gdprApplies !== false) { payload.gdpr_consent = gdprConsent.consentString; } } - // US Privace (CCPA) support + // US Privacy (CCPA) support if (uspConsent) { payload.us_privacy = uspConsent; } @@ -208,7 +217,7 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { } const jsonPayload = JSON.stringify(payload); - ajax(s2sConfig.syncEndpoint, + ajax(getMatchingConsentUrl(s2sConfig.syncEndpoint, gdprConsent), (response) => { try { response = JSON.parse(response); @@ -288,11 +297,20 @@ function doBidderSync(type, url, bidder, done) { * * @param {Array} bidders a list of bidder names */ -function doClientSideSyncs(bidders) { +function doClientSideSyncs(bidders, gdprConsent, uspConsent) { bidders.forEach(bidder => { let clientAdapter = adapterManager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { - clientAdapter.registerSyncs([]); + config.runWithBidder( + bidder, + utils.bind.call( + clientAdapter.registerSyncs, + clientAdapter, + [], + gdprConsent, + uspConsent + ) + ); } }); } @@ -336,18 +354,18 @@ function addBidderFirstPartyDataToRequest(request) { const bidderConfig = config.getBidderConfig(); const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.fpd) { - const fpd = {}; - if (currBidderConfig.fpd.context) { - fpd.site = currBidderConfig.fpd.context; + if (currBidderConfig.ortb2) { + const ortb2 = {}; + if (currBidderConfig.ortb2.site) { + ortb2.site = currBidderConfig.ortb2.site; } - if (currBidderConfig.fpd.user) { - fpd.user = currBidderConfig.fpd.user; + if (currBidderConfig.ortb2.user) { + ortb2.user = currBidderConfig.ortb2.user; } acc.push({ bidders: [ bidder ], - config: { fpd } + config: { ortb2 } }); } return acc; @@ -607,6 +625,7 @@ const OPEN_RTB_PROTOCOL = { } // get bidder params in form { : {...params} } + // initialize reduce function with the user defined `ext` properties on the ad unit const ext = adUnit.bids.reduce((acc, bid) => { const adapter = adapterManager.bidderRegistry[bid.bidder]; if (adapter && adapter.getSpec().transformBidParams) { @@ -614,27 +633,36 @@ const OPEN_RTB_PROTOCOL = { } acc[bid.bidder] = (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params; return acc; - }, {}); + }, {...utils.deepAccess(adUnit, 'ortb2Imp.ext')}); const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + const ortb2 = {...utils.deepAccess(adUnit, 'ortb2Imp.ext.data')}; + Object.keys(ortb2).forEach(prop => { + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + if (prop === 'pbadslot') { + if (typeof ortb2[prop] === 'string' && ortb2[prop]) { + utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + } else { + // remove pbadslot property if it doesn't meet the spec + delete imp.ext.data.pbadslot; + } + } else if (prop === 'adserver') { + /** + * Copy GAM AdUnit and Name to imp + */ + ['name', 'adslot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(ortb2, `adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); @@ -720,7 +748,7 @@ const OPEN_RTB_PROTOCOL = { } if (!utils.isEmpty(aliases)) { - request.ext.prebid.aliases = aliases; + request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); @@ -739,6 +767,21 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); } + const multibid = config.getConfig('multibid'); + if (multibid) { + utils.deepSetValue(request, 'ext.prebid.multibid', multibid.reduce((result, i) => { + let obj = {}; + + Object.keys(i).forEach(key => { + obj[key.toLowerCase()] = i[key]; + }); + + result.push(obj); + + return result; + }, [])); + } + if (bidRequests) { if (firstBidRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module @@ -763,12 +806,12 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('fpd') || {}; - if (commonFpd.context) { - utils.deepSetValue(request, 'site.ext.data', commonFpd.context); + const commonFpd = getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.mergeDeep(request, {site: commonFpd.site}); } if (commonFpd.user) { - utils.deepSetValue(request, 'user.ext.data', commonFpd.user); + utils.mergeDeep(request, {user: commonFpd.user}); } addBidderFirstPartyDataToRequest(request); @@ -930,6 +973,7 @@ const OPEN_RTB_PROTOCOL = { if (bid.burl) { bidObject.burl = bid.burl; } bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; bidObject.meta = bidObject.meta || {}; + if (bid.ext && bid.ext.dchain) { bidObject.meta.dchain = utils.deepClone(bid.ext.dchain); } if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). @@ -961,6 +1005,29 @@ function bidWonHandler(bid) { } } +function hasPurpose1Consent(gdprConsent) { + let result = true; + if (gdprConsent) { + if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { + result = !!(utils.deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +function getMatchingConsentUrl(urlProp, gdprConsent) { + return hasPurpose1Consent(gdprConsent) ? urlProp.p1Consent : urlProp.noP1Consent; +} + +function getConsentData(bidRequests) { + let gdprConsent, uspConsent; + if (Array.isArray(bidRequests) && bidRequests.length > 0) { + gdprConsent = bidRequests[0].gdprConsent; + uspConsent = bidRequests[0].uspConsent; + } + return { gdprConsent, uspConsent }; +} + /** * Bidder adapter for Prebid Server */ @@ -970,6 +1037,7 @@ export function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { const adUnits = utils.deepClone(s2sBidRequest.ad_units); + let { gdprConsent, uspConsent } = getConsentData(bidRequests); // at this point ad units should have a size array either directly or mapped so filter for that const validAdUnits = adUnits.filter(unit => @@ -983,13 +1051,7 @@ export function PrebidServer() { .filter(utils.uniques); if (Array.isArray(_s2sConfigs)) { - if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint) { - let gdprConsent, uspConsent; - if (Array.isArray(bidRequests) && bidRequests.length > 0) { - gdprConsent = bidRequests[0].gdprConsent; - uspConsent = bidRequests[0].uspConsent; - } - + if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { let syncBidders = s2sBidRequest.s2sConfig.bidders .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); @@ -1002,7 +1064,7 @@ export function PrebidServer() { utils.logInfo('BidRequest: ' + requestJson); if (request && requestJson) { ajax( - s2sBidRequest.s2sConfig.endpoint, + getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent), { success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done, s2sBidRequest.s2sConfig), error: done @@ -1018,6 +1080,7 @@ export function PrebidServer() { function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done, s2sConfig) { let result; let bids = []; + let { gdprConsent, uspConsent } = getConsentData(bidderRequests); try { result = JSON.parse(response); @@ -1044,7 +1107,7 @@ export function PrebidServer() { } done(); - doClientSideSyncs(requestedBidders); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); } // Listen for bid won to call wurl diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index c546337c47f..ff07ee9ede9 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,62 +1,79 @@ - import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'proxistore'; -const storage = getStorageManager(); const PROXISTORE_VENDOR_ID = 418; function _createServerRequest(bidRequests, bidderRequest) { - const sizeIds = []; - + var sizeIds = []; bidRequests.forEach(function (bid) { - const sizeId = { + var sizeId = { id: bid.bidId, sizes: bid.sizes.map(function (size) { return { width: size[0], - height: size[1] + height: size[1], }; - }) + }), + floor: _assignFloor(bid), + segments: _assignSegments(bid), }; sizeIds.push(sizeId); }); - const payload = { + var payload = { auctionId: bidRequests[0].auctionId, transactionId: bidRequests[0].auctionId, bids: sizeIds, website: bidRequests[0].params.website, language: bidRequests[0].params.language, gdpr: { - applies: false - } - }; - const options = { - contentType: 'application/json', - withCredentials: true + applies: false, + consentGiven: false + }, }; if (bidderRequest && bidderRequest.gdprConsent) { - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) { + const { gdprConsent } = bidderRequest; + if (typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { payload.gdpr.applies = true; } - if (typeof bidderRequest.gdprConsent.consentString === 'string' && bidderRequest.gdprConsent.consentString) { + if (typeof gdprConsent.consentString === 'string' && gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } - - if (bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents && typeof bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { - payload.gdpr.consentGiven = !!bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + if (gdprConsent.vendorData) { + const {vendorData} = gdprConsent; + const {apiVersion} = gdprConsent; + if (apiVersion === 2 && vendorData.vendor && vendorData.vendor.consents && typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { + payload.gdpr.consentGiven = !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; + } else if (apiVersion === 1 && vendorData.vendorConsents && typeof vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { + payload.gdpr.consentGiven = !!vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + } } } + const options = { + contentType: 'application/json', + withCredentials: payload.gdpr.consentGiven, + }; + + const endPointUri = payload.gdpr.consentGiven || !payload.gdpr.applies + ? `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi` + : `https://abs.cookieless-proxistore.com/${payload.language}/v3/rtb/prebid/multi`; + return { method: 'POST', - url: bidRequests[0].params.url || 'https://abs.proxistore.com/' + payload.language + '/v3/rtb/prebid/multi', + url: endPointUri, data: JSON.stringify(payload), - options: options + options: options, }; } +function _assignSegments(bid) { + if (bid.ortb2 && bid.ortb2.user && bid.ortb2.user.ext && bid.ortb2.user.ext.data) { + return bid.ortb2.user.ext.data || {segments: [], contextual_categories: {}}; + } + return {segments: [], contextual_categories: {}}; +} + function _createBidResponse(response) { return { requestId: response.requestId, @@ -70,7 +87,7 @@ function _createBidResponse(response) { netRevenue: response.netRevenue, vastUrl: response.vastUrl, vastXml: response.vastXml, - dealId: response.dealId + dealId: response.dealId, }; } /** @@ -81,23 +98,8 @@ function _createBidResponse(response) { */ function isBidRequestValid(bid) { - const hasNoAd = function() { - if (!storage.hasLocalStorage()) { - return false; - } - const pxNoAds = storage.getDataFromLocalStorage(`PX_NoAds_${bid.params.website}`); - if (!pxNoAds) { - return false; - } else { - const storedDate = new Date(pxNoAds); - const now = new Date(); - const diff = Math.abs(storedDate.getTime() - now.getTime()) / 60000; - return diff <= 5; - } - } - return !!(bid.params.website && bid.params.language) && !hasNoAd(); + return !!(bid.params.website && bid.params.language); } - /** * Make a server request from the list of BidRequests. * @@ -107,7 +109,7 @@ function isBidRequestValid(bid) { */ function buildRequests(bidRequests, bidderRequest) { - const request = _createServerRequest(bidRequests, bidderRequest); + var request = _createServerRequest(bidRequests, bidderRequest); return request; } /** @@ -119,34 +121,23 @@ function buildRequests(bidRequests, bidderRequest) { */ function interpretResponse(serverResponse, bidRequest) { - const itemName = `PX_NoAds_${websiteFromBidRequest(bidRequest)}`; - if (serverResponse.body.length > 0) { - storage.removeDataFromLocalStorage(itemName, true); - return serverResponse.body.map(_createBidResponse); - } else { - storage.setDataInLocalStorage(itemName, new Date()); - return []; - } + return serverResponse.body.map(_createBidResponse); } -const websiteFromBidRequest = function(bidR) { - if (bidR.data) { - return JSON.parse(bidR.data).website - } else if (bidR.params.website) { - return bidR.params.website; - } -} +function _assignFloor(bid) { + if (typeof bid.getFloor === 'function') { + var floorInfo = bid.getFloor({ + currency: 'EUR', + mediaType: 'banner', + size: '*', + }); -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param syncOptions Which user syncs are allowed? - * @param serverResponses List of server's responses. - * @return The user syncs which should be dropped. - */ + if (floorInfo.currency === 'EUR') { + return floorInfo.floor; + } + } -function getUserSyncs(syncOptions, serverResponses) { - return []; + return null; } export const spec = { @@ -154,7 +145,7 @@ export const spec = { isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, - getUserSyncs: getUserSyncs + }; registerBidder(spec); diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 5c750e66c25..2df2d25f627 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -7,8 +7,8 @@ import { deepSetValue, inIframe, isArrayOfNums, + isFn, isInteger, - isNumber, isStr, logError, parseQueryStringParameters, @@ -185,9 +185,16 @@ function buildImp(bid) { imp.video = buildVideoParams(bid.mediaTypes.video, bid.params.video); } - const bidFloor = bid.params.bidFloor; - if (isNumber(bidFloor)) { - imp.bidfloor = bidFloor; + if (isFn(bid.getFloor)) { + const { floor } = bid.getFloor({ + mediaType: bid.mediaTypes.banner ? 'banner' : 'video', + size: '*', + currency: 'USD', + }); + + if (floor) { + imp.bidfloor = floor; + } } const pos = bid.params.position; diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index ff23a433331..66e0c382285 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -12,8 +12,6 @@ Module that connects to pubGENIUS's demand sources # Test Parameters -Test bids have $0.01 CPM by default. Use `bidFloor` in bidder params to control CPM for testing purposes. - ``` var adUnits = [ { @@ -45,7 +43,6 @@ var adUnits = [ bidder: 'pubgenius', params: { adUnitId: '1000', - bidFloor: 0.5, test: true } } @@ -66,7 +63,6 @@ var adUnits = [ bidder: 'pubgenius', params: { adUnitId: '1001', - bidFloor: 1, test: true, // other video parameters as in OpenRTB v2.5 spec diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 07e5b62ac26..c6ecea48abf 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -101,7 +101,7 @@ const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ } ] -const NET_REVENUE = false; +const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', 5: 'PREF', @@ -645,6 +645,8 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } + _addImpressionFPD(impObj, bid); + _addFloorFromFloorModule(impObj, bid); return impObj.hasOwnProperty(BANNER) || @@ -652,6 +654,36 @@ function _createImpressionObject(bid, conf) { impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } +function _addImpressionFPD(imp, bid) { + const ortb2 = {...utils.deepAccess(bid, 'ortb2Imp.ext.data')}; + Object.keys(ortb2).forEach(prop => { + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + if (prop === 'pbadslot') { + if (typeof ortb2[prop] === 'string' && ortb2[prop]) utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + } else if (prop === 'adserver') { + /** + * Copy GAM AdUnit and Name to imp + */ + ['name', 'adslot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(ortb2, `adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + // copy GAM ad unit id as imp[].ext.dfp_ad_unit_code + if (name === 'adslot') { + utils.deepSetValue(imp, `ext.dfp_ad_unit_code`, value); + } + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); + } + }); +} + function _addFloorFromFloorModule(impObj, bid) { let bidFloor = -1; // get lowest floor from floorModule @@ -998,6 +1030,15 @@ export const spec = { _handleEids(payload, validBidRequests); _blockedIabCategoriesValidation(payload, blockedIabCategories); + // First Party Data + const commonFpd = config.getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.mergeDeep(payload, {site: commonFpd.site}); + } + if (commonFpd.user) { + utils.mergeDeep(payload, {user: commonFpd.user}); + } + // Note: Do not move this block up // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object if (typeof config.getConfig('app') === 'object') { diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 894752d607b..136b328d381 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -6,15 +6,19 @@ import * as utils from '../src/utils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.0.0'; +const pubxaiAnalyticsVersion = 'v1.1.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; let initOptions; let auctionTimestamp; +let auctionCache = []; let events = { - bids: [] + bids: [], + floorDetail: {}, + pageDetail: {}, + deviceDetail: {} }; var pubxaiAnalyticsAdapter = Object.assign(adapter( @@ -27,14 +31,14 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { args.forEach(item => { mapBidResponse(item, 'timeout'); }); } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + events.auctionInit = args; events.floorDetail = {}; - if (typeof args.bidderRequests[0].bids[0] !== 'undefined' && typeof args.bidderRequests[0].bids[0].floorData !== 'undefined') { - Object.assign(events.floorDetail, args.bidderRequests[0].bids[0].floorData); + events.bids = []; + const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (typeof floorData !== 'undefined') { + Object.assign(events.floorDetail, floorData); } auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { - events.bids = []; - mapBidRequests(args).forEach(item => { events.bids.push(item) }); } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); } else if (eventType === CONSTANTS.EVENTS.BID_WON) { @@ -43,81 +47,48 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( }, 'bidwon'); } } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { send(events, 'auctionEnd'); } } }); -function mapBidRequests(params) { - let arr = []; - if (typeof params.bids !== 'undefined' && params.bids.length) { - params.bids.forEach(function (bid) { - arr.push({ - bidderCode: bid.bidder, - bidId: bid.bidId, - adUnitCode: bid.adUnitCode, - requestId: bid.bidderRequestId, - auctionId: bid.auctionId, - placementId: bid.params.placementId, - floorData: bid.floorData, - transactionId: bid.transactionId, - sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), - renderStatus: 1, - requestTimestamp: params.auctionStart - }); - }); - } - return arr; -} - function mapBidResponse(bidResponse, status) { - if (status !== 'bidwon') { - let bid = events.bids.filter(o => o.bidId === bidResponse.bidId || o.bidId === bidResponse.requestId)[0]; - Object.assign(bid, { - bidderCode: bidResponse.bidder, - bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, + if (typeof bidResponse !== 'undefined') { + let bid = { adUnitCode: bidResponse.adUnitCode, auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - floorData: bidResponse.floorData, - status: bidResponse.status, - renderStatus: status === 'timeout' ? 3 : 2, - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp, - platform: navigator.platform, - deviceType: getDeviceType() - }); - } else { - return { bidderCode: bidResponse.bidder, - bidId: bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, + cpm: bidResponse.cpm, creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, floorData: bidResponse.floorData, - renderedSize: bidResponse.size, mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: 4, - timeToRespond: bidResponse.timeToRespond, + netRevenue: bidResponse.netRevenue, requestTimestamp: bidResponse.requestTimestamp, responseTimestamp: bidResponse.responseTimestamp, - platform: navigator.platform, - deviceType: getDeviceType() + status: bidResponse.status, + statusMessage: bidResponse.statusMessage, + timeToRespond: bidResponse.timeToRespond, + transactionId: bidResponse.transactionId + }; + if (status !== 'bidwon') { + Object.assign(bid, { + bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, + renderStatus: status === 'timeout' ? 3 : 2, + sizes: utils.parseSizesInput(bidResponse.size).toString(), + }); + events.bids.push(bid); + } else { + Object.assign(bid, { + bidId: bidResponse.requestId, + floorProvider: events.floorDetail ? events.floorDetail.floorProvider : null, + isWinningBid: true, + placementId: bidResponse.params ? utils.deepAccess(bidResponse, 'params.0.placementId') : null, + renderedSize: bidResponse.size, + renderStatus: 4 + }); + return bid; } } } @@ -132,6 +103,26 @@ export function getDeviceType() { return 'desktop'; } +export function getBrowser() { + if (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)) return 'Chrome'; + else if (navigator.userAgent.match('CriOS')) return 'Chrome'; + else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; + else if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; + else if (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor)) return 'Safari'; + else if (/Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent)) return 'Internet Explorer'; + else return 'Others'; +} + +export function getOS() { + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; +} + // add sampling rate pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { return (Math.floor((Math.random() * samplingRate + 1)) === parseInt(samplingRate)); @@ -140,12 +131,24 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { let location = utils.getWindowLocation(); - if (typeof data !== 'undefined') { - data.pageDetail = {}; - Object.assign(data.pageDetail, { host: location.host, path: location.pathname, search: location.search }); - } data.initOptions = initOptions; - + if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { + Object.assign(data.pageDetail, { + host: location.host, + path: location.pathname, + search: location.search, + adUnitCount: data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null + }); + data.initOptions.auctionId = data.auctionInit.auctionId; + delete data.auctionInit; + } + data.deviceDetail = {}; + Object.assign(data.deviceDetail, { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser() + }); let pubxaiAnalyticsRequestUrl = utils.buildUrl({ protocol: 'https', hostname: (initOptions && initOptions.hostName) || defaultHost, @@ -156,8 +159,12 @@ function send(data, status) { prebidVersion: $$PREBID_GLOBAL$$.version } }); - - ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + if (status == 'bidwon') { + ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); + } else if (status == 'auctionEnd' && auctionCache.indexOf(data.initOptions.auctionId) === -1) { + ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); + auctionCache.push(data.initOptions.auctionId); + } } } diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 7cb83520979..e2782d82512 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -24,16 +24,32 @@ export const spec = { }) }) + let payload = { + requestId: bidderRequest.bidderRequestId, + bids, + referer: bidderRequest.refererInfo.referer + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdprConsent = { + consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : false, + consentString: bidderRequest.gdprConsent.consentString, + gdpr: bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0 + } + } + + const options = { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + return { method: 'POST', url: ENDPOINT, - data: { requestId: bidderRequest.bidderRequestId, bids }, - options: { - contentType: 'application/json', - customHeaders: { - 'Rtb-Direct': true - } - } + data: payload, + options }; }, diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index 1f07cc07b91..8f3cbe02f23 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -57,7 +57,7 @@ export const spec = { bid_id: bidId, }; } - prepareExtraParams(params, payload); + prepareExtraParams(params, payload, bidderRequest); return { method: 'GET', @@ -97,9 +97,46 @@ export const spec = { bidResponses.push(bidResponse); } return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = [] + + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (syncOptions.iframeEnabled) { + serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ + type: 'iframe', + url: appendToUrl(url, gdprParams) + })); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ + type: 'image', + url: appendToUrl(url, gdprParams) + })); + } + return syncs; } } +function appendToUrl(url, what) { + if (!what) { + return url; + } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; +} + function objectToQueryString(obj, prefix) { let str = []; let p; @@ -125,10 +162,23 @@ function isVideoRequest(bid) { return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video'); } -function prepareExtraParams(params, payload) { +function prepareExtraParams(params, payload, bidderRequest) { if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } + + if (bidderRequest && bidderRequest.gdprConsent) { + if (payload.pfilter !== undefined) { + payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; + } else { + payload.pfilter = { + 'gdpr_consent': bidderRequest.gdprConsent.consentString, + 'gdpr': bidderRequest.gdprConsent.gdprApplies + }; + } + } + if (params.bcat !== undefined) { payload.bcat = params.bcat; } diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 2f4173f240b..31e430d79f9 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -46,6 +46,19 @@ export const spec = { } }; + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString || '' + }, + }; + request.regs = { + ext: { + gdpr: bidderRequest.gdprConsent.gdprApplies !== undefined ? bidderRequest.gdprConsent.gdprApplies : true + } + }; + } + return { method: 'POST', url: ENDPOINT, @@ -87,6 +100,11 @@ function bidResponseAvailable(bidRequest, bidResponse) { currency: bidResponse.cur, native: nativeResponse(idToImpMap[id], idToBidMap[id]) }; + if (idToBidMap[id].adomain) { + bid.meta = { + advertiserDomains: idToBidMap[id].adomain + } + } bids.push(bid); } }); @@ -94,11 +112,20 @@ function bidResponseAvailable(bidRequest, bidResponse) { } function impression(slot) { + let bidFloorFromModule + if (typeof slot.getFloor === 'function') { + const floorInfo = slot.getFloor({ + currency: 'USD', + mediaType: 'native', + size: '\*' + }); + bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined; + } return { id: slot.bidId, native: nativeImpression(slot), - bidfloor: slot.params.bidfloor || 0, - bidfloorcur: slot.params.bidfloorcur || 'USD', + bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, + bidfloorcur: (bidFloorFromModule && 'USD') || slot.params.bidfloorcur || 'USD', tagId: slot.params.tagId || '0' }; } diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 1acbcfd0463..fa090044f05 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -253,6 +253,9 @@ function RhythmOneBidAdapter() { cpm: parseFloat(bid.price), width: bid.w, height: bid.h, + meta: { + advertiserDomains: bid.adomain + }, creativeId: bid.crid, currency: 'USD', netRevenue: true, diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 07de3e40594..5e2a5e1bff5 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -14,24 +14,24 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ isBidRequestValid: function (bid) { return !!(bid.params && bid.params.pid && bid.params.supplyType); }, /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { - bidfloor: bid.params.bidfloor, + bidfloor: raiGetFloor(bid, config), ifa: bid.params.ifa, pid: bid.params.pid, supplyType: bid.params.supplyType, @@ -77,11 +77,11 @@ export const spec = { }); }, /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; // try catch @@ -103,10 +103,16 @@ export const spec = { if (response.media_type === 'video') { bidResponse.vastXml = response.vastXML; try { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { - bidResponse.renderer = Renderer.install({ - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } bidResponse.renderer.setRender(renderer); } } catch (e) { @@ -121,13 +127,13 @@ export const spec = { return bidResponses }, /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { const syncs = []; @@ -135,11 +141,15 @@ export const spec = { var syncUrl = ''; var consent = ''; + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { consent = `consentString=${gdprConsent.consentString}` } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand if (consent != '') { syncUrl += `&${consent}` @@ -150,7 +160,7 @@ export const spec = { }); } - if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0) { + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; if (consent != '') { syncUrl += `&${consent}` @@ -197,6 +207,10 @@ function raiGetVideoInfo(bid) { playerSize: bid.mediaTypes.video.playerSize, mimes: bid.mediaTypes.video.mimes }; + } else { + videoData = { + format: 'banner' + } } return videoData; } @@ -253,3 +267,42 @@ function raiGetResolution() { } return resolution; } + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + raiFloor = bid.params.bidfloor; + } else if (typeof bid.getFloor == 'function') { + let floorSpec = bid.getFloor({ + currency: config.getConfig('currency.adServerCurrency'), + mediaType: bid.mediaType.banner ? 'banner' : 'video', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index fbf59a0208a..f888117b166 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,7 +39,7 @@ Please reach out to your account manager for more information. "pid":"ADb1f40rmo", "supplyType":"site", "bidfloor":0.40, - "keywords": "bici=scott;coche=audi;coche=mercedes;" + "keywords": "key1=value1;key2=value2;key3=value3;" } }] } diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..e3265ad5d3e 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 3337c3f1b59..6b8fe05e8bb 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -302,6 +302,9 @@ function interpretBannerBid(serverBid) { width: serverBid.w, height: serverBid.h, ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, netRevenue: true, currency: 'USD' } @@ -320,6 +323,9 @@ function interpretNativeBid(serverBid) { width: 1, height: 1, ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, netRevenue: true, currency: 'USD', native: interpretNativeAd(serverBid.adm), diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 08ae8bf2dd8..3237facb2e7 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -119,6 +119,7 @@ function sendMessage(auctionId, bidWonId) { function formatBid(bid) { return utils.pick(bid, [ 'bidder', + 'bidderDetail', 'bidId', bidId => utils.deepAccess(bid, 'bidResponse.pbsBidId') || utils.deepAccess(bid, 'bidResponse.seatBidId') || bidId, 'status', 'error', @@ -230,7 +231,8 @@ function sendMessage(auctionId, bidWonId) { clientTimeoutMillis: auctionCache.timeout, samplingFactor, accountId, - adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]) + adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), + requestId: auctionId }; // pick our of top level floor data we want to send! @@ -362,7 +364,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { const height = bid.height || bid.playerHeight; return (width && height) ? {width, height} : undefined; }, - 'seatBidId', + // Handling use case where pbs sends back 0 or '0' bidIds + 'pbsBidId', pbsBidId => pbsBidId == 0 ? utils.generateUUID() : pbsBidId, + 'seatBidId', seatBidId => seatBidId == 0 ? utils.generateUUID() : seatBidId, 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined, @@ -482,8 +486,8 @@ function subscribeToGamSlots() { // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number 'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => utils.isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => utils.isNumber(lineItemId) ? lineItemId : undefined, + 'creativeId', creativeId => utils.isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : utils.isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => utils.isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : utils.isNumber(lineItemId) ? lineItemId : undefined, 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); @@ -659,12 +663,12 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { return ['banner']; }, 'gam', () => { - if (utils.deepAccess(bid, 'fpd.context.adServer.name') === 'gam') { - return {adSlot: bid.fpd.context.adServer.adSlot} + if (utils.deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { + return {adSlot: bid.ortb2Imp.ext.data.adserver.adSlot} } }, - 'pbAdSlot', () => utils.deepAccess(bid, 'fpd.context.pbAdSlot'), - 'pattern', () => utils.deepAccess(bid, 'fpd.context.aupName') + 'pbAdSlot', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), + 'pattern', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.aupname') ]) ]); return memo; @@ -672,6 +676,13 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { break; case BID_RESPONSE: let auctionEntry = cache.auctions[args.auctionId]; + + if (!auctionEntry.bids[args.requestId] && args.originalRequestId) { + auctionEntry.bids[args.requestId] = {...auctionEntry.bids[args.originalRequestId]}; + auctionEntry.bids[args.requestId].bidId = args.requestId; + auctionEntry.bids[args.requestId].bidderDetail = args.targetingBidder; + } + let bid = auctionEntry.bids[args.requestId]; // If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name if (!utils.deepAccess(bid, 'adUnit.gam.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 7297c82440f..6c6ac83318a 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -3,9 +3,13 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; +import { Renderer } from '../src/Renderer.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; +const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.0.0.js'; +// renderer code at https://github.com/rubicon-project/apex2 let rubiConf = {}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire @@ -105,7 +109,8 @@ var sizeMap = { 288: '640x380', 548: '500x1000', 550: '980x480', - 552: '300x200' + 552: '300x200', + 558: '640x640' }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); @@ -196,6 +201,11 @@ export const spec = { } } + let modules = (getGlobal()).installedModules; + if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { + utils.deepSetValue(data, 'ext.prebid.analytics', [{ 'adapter': 'rubicon', 'client-analytics': true }]); + } + let bidFloor; if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { let floorInfo; @@ -256,6 +266,21 @@ export const spec = { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } + const multibid = config.getConfig('multibid'); + if (multibid) { + utils.deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { + let obj = {}; + + Object.keys(i).forEach(key => { + obj[key.toLowerCase()] = i[key]; + }); + + result.push(obj); + + return result; + }, [])); + } + applyFPD(bidRequest, VIDEO, data); // if storedAuctionResponse has been set, pass SRID @@ -510,6 +535,8 @@ export const spec = { data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); } + data['rp_maxbids'] = bidderRequest.bidLimit || 1; + applyFPD(bidRequest, BANNER, data); if (config.getConfig('coppa') === true) { @@ -628,6 +655,11 @@ export const spec = { if (bid.adm) { bidObject.vastXml = bid.adm; } if (bid.nurl) { bidObject.vastUrl = bid.nurl; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } + + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + if (videoContext.toLowerCase() === 'outstream') { + bidObject.renderer = outstreamRenderer(bidObject); + } } else { utils.logWarn('Rubicon: video response received non-video media type'); } @@ -640,6 +672,8 @@ export const spec = { } let ads = responseObj.ads; + let lastImpId; + let multibid = 0; // video ads array is wrapped in an object if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { @@ -652,12 +686,14 @@ export const spec = { } return ads.reduce((bids, ad, i) => { + (ad.impression_id && lastImpId === ad.impression_id) ? multibid++ : lastImpId = ad.impression_id; + if (ad.status !== 'ok') { return bids; } // associate bidRequests; assuming ads matches bidRequest - const associatedBidRequest = Array.isArray(bidRequest) ? bidRequest[i] : bidRequest; + const associatedBidRequest = Array.isArray(bidRequest) ? bidRequest[i - multibid] : bidRequest; if (associatedBidRequest && typeof associatedBidRequest === 'object') { let bid = { @@ -783,6 +819,64 @@ function _renderCreative(script, impId) { `; } +function hideGoogleAdsDiv(adUnit) { + const el = adUnit.querySelector("div[id^='google_ads']"); + if (el) { + el.style.setProperty('display', 'none'); + } +} + +function hideSmartAdServerIframe(adUnit) { + const el = adUnit.querySelector("script[id^='sas_script']"); + const nextSibling = el && el.nextSibling; + if (nextSibling && nextSibling.localName === 'iframe') { + nextSibling.style.setProperty('display', 'none'); + } +} + +function renderBid(bid) { + // hide existing ad units + const adUnitElement = document.getElementById(bid.adUnitCode); + hideGoogleAdsDiv(adUnitElement); + hideSmartAdServerIframe(adUnitElement); + + // configure renderer + const config = bid.renderer.getConfig(); + bid.renderer.push(() => { + window.MagniteApex.renderAd({ + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + placement: { + attachTo: `#${bid.adUnitCode}`, + align: config.align || 'center', + position: config.position || 'append' + }, + closeButton: config.closeButton || false, + label: config.label || undefined, + collapse: config.collapse || true + }); + }); +} + +function outstreamRenderer(rtbBid) { + const renderer = Renderer.install({ + id: rtbBid.adId, + url: rubiConf.rendererUrl || DEFAULT_RENDERER_URL, + config: rubiConf.rendererConfig || {}, + loaded: false, + adUnitCode: rtbBid.adUnitCode + }); + + try { + renderer.setRender(renderBid); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + function parseSizes(bid, mediaType) { let params = bid.params; if (mediaType === 'video') { @@ -871,21 +965,27 @@ function addVideoParameters(data, bidRequest) { } function applyFPD(bidRequest, mediaType, data) { - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} + const BID_FPD = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + if (bidRequest.params.keywords) BID_FPD.site.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd); - - const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; - let obj = {}; - let impData = {}; - let keywords = []; + let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key) { - if (typeof prop === 'object' && !Array.isArray(prop)) { + if (key === 'data' && Array.isArray(prop)) { + return prop.filter(name => name.segment && utils.deepAccess(name, 'ext.taxonomyname') && + utils.deepAccess(name, 'ext.taxonomyname').match(/iab/i)).map(value => { + let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { + result.push(obj.id); + return result; + }, []); + if (segments.length > 0) return segments.toString(); + }).toString(); + } else if (typeof prop === 'object' && !Array.isArray(prop)) { utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { @@ -895,53 +995,43 @@ function applyFPD(bidRequest, mediaType, data) { }).toString() : prop.toString(); } }; - - Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => { - if (key === 'keywords') { - if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] - - result[key] = fpd[type][key]; - - if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); - } else if (key === 'data') { - utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); - } else if (key === 'adServer' || key === 'pbAdSlot') { - (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { - const value = validate(fpd[type][key][name]); - if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, '')) - }) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '') - } else { - utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); - } - - return result; - }, {}); - - if (mediaType === BANNER) { - let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; - - Object.keys(duplicate).forEach((key) => { - const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); - - if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val; - }); - } - }); + const addBannerData = function(obj, name, key, isParent = true) { + let val = validate(obj, key); + let loc = (MAP[key] && isParent) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; + data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; + } Object.keys(impData).forEach((key) => { - if (mediaType === BANNER) { - (map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key]; - } else { - utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}}); + if (key === 'adserver') { + ['name', 'adslot'].forEach(prop => { + if (impData[key][prop]) impData[key][prop] = impData[key][prop].toString().replace(/^\/+/, ''); + }); + } else if (key === 'pbadslot') { + impData[key] = impData[key].toString().replace(/^\/+/, ''); } }); if (mediaType === BANNER) { - let kw = validate(keywords, 'keywords'); - if (kw) data.kw = kw; + ['site', 'user'].forEach(name => { + Object.keys(fpd[name]).forEach((key) => { + if (key !== 'ext') { + addBannerData(fpd[name][key], name, key); + } else if (fpd[name][key].data) { + Object.keys(fpd[name].ext.data).forEach((key) => { + addBannerData(fpd[name].ext.data[key], name, key, false); + }); + } + }); + }); + Object.keys(impData).forEach((key) => { + (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + }); } else { - utils.mergeDeep(data, obj); + if (Object.keys(impData).length) { + utils.mergeDeep(data.imp[0].ext, {data: impData}); + } + + utils.mergeDeep(data, fpd); } } @@ -1092,7 +1182,6 @@ export function hasValidVideoParams(bid) { var requiredParams = { mimes: arrayType, protocols: arrayType, - maxduration: numberType, linearity: numberType, api: arrayType } diff --git a/modules/rubiconBidAdapter.md b/modules/rubiconBidAdapter.md index beb29a6baf1..0ef22a81972 100644 --- a/modules/rubiconBidAdapter.md +++ b/modules/rubiconBidAdapter.md @@ -70,9 +70,9 @@ globalsupport@rubiconproject.com for more information. bids: [{ bidder: 'rubicon', params: { - accountId: '7780', - siteId: '87184', - zoneId: '412394', + accountId: 7780, + siteId: 87184, + zoneId: 412394, video: { language: 'en' } diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index e1832e50020..c25d833a71c 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -6,12 +6,39 @@ const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; const SEEDTAG_SSP_ONTIMEOUT_ENDPOINT = 'https://s.seedtag.com/se/hb/timeout'; - +const ALLOWED_PLACEMENTS = { + inImage: true, + inScreen: true, + inArticle: true, + banner: true, + video: true +} const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' }; +const deviceConnection = { + FIXED: 'fixed', + MOBILE: 'mobile', + UNKNOWN: 'unknown' +}; + +const getConnectionType = () => { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {} + switch (connection.type || connection.effectiveType) { + case 'wifi': + case 'ethernet': + return deviceConnection.FIXED + case 'cellular': + case 'wimax': + return deviceConnection.MOBILE + default: + const isMobile = /iPad|iPhone|iPod/.test(navigator.userAgent) || /android/i.test(navigator.userAgent) + return isMobile ? deviceConnection.UNKNOWN : deviceConnection.FIXED + } +}; + function mapMediaType(seedtagMediaType) { if (seedtagMediaType === 'display') return BANNER; if (seedtagMediaType === 'video') return VIDEO; @@ -27,10 +54,7 @@ function hasMandatoryParams(params) { !!params.publisherId && !!params.adUnitId && !!params.placement && - (params.placement === 'inImage' || - params.placement === 'inScreen' || - params.placement === 'banner' || - params.placement === 'video') + !!ALLOWED_PLACEMENTS[params.placement] ); } @@ -52,13 +76,14 @@ function buildBidRequests(validBidRequests) { return mediaTypesMap[pbjsType]; } ); + const bidRequest = { id: validBidRequest.bidId, transactionId: validBidRequest.transactionId, sizes: validBidRequest.sizes, supplyTypes: mediaTypes, adUnitId: params.adUnitId, - placement: params.placement + placement: params.placement, }; if (params.adPosition) { @@ -88,8 +113,10 @@ function buildBid(seedtagBid) { currency: seedtagBid.currency, netRevenue: true, mediaType: mediaType, - ttl: seedtagBid.ttl + ttl: seedtagBid.ttl, + nurl: seedtagBid.nurl }; + if (mediaType === VIDEO) { bid.vastXml = seedtagBid.content; } else { @@ -142,6 +169,7 @@ export const spec = { cmp: !!bidderRequest.gdprConsent, timeout: bidderRequest.timeout, version: '$prebid.version$', + connectionType: getConnectionType(), bidRequests: buildBidRequests(validBidRequests) }; @@ -198,8 +226,18 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout(data) { - getTimeoutUrl(data); - utils.triggerPixel(SEEDTAG_SSP_ONTIMEOUT_ENDPOINT); + const url = getTimeoutUrl(data); + utils.triggerPixel(url); + }, + + /** + * Function to call when the adapter wins the auction + * @param {bid} Bid information received from the server + */ + onBidWon: function (bid) { + if (bid && bid.nurl) { + utils.triggerPixel(bid.nurl); + } } } registerBidder(spec); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 24be8673615..eef18288b17 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; -const VERSION = '3.3.1'; +const VERSION = '3.3.2'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -48,6 +49,10 @@ export const sharethroughAdapterSpec = { query.us_privacy = bidderRequest.uspConsent } + if (config.getConfig('coppa') === true) { + query.coppa = true + } + if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js new file mode 100644 index 00000000000..d5734d23fdc --- /dev/null +++ b/modules/shinezBidAdapter.js @@ -0,0 +1,83 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'shinez'; +const BIDDER_SHORT_CODE = 'shz'; +const ADAPTER_VERSION = '1.0.0'; + +const TARGET_URL = 'https://shinez-ssp.shinez.workers.dev/prebid'; + +export const spec = { + code: BIDDER_CODE, + version: ADAPTER_VERSION, + aliases: { + code: BIDDER_SHORT_CODE + }, + supportedMediaTypes: [ BANNER ], + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, +}; + +export const internal = { + TARGET_URL +} + +function isBidRequestValid(bid) { + return !!(bid && bid.params && + bid.params.placementId && typeof bid.params.placementId === 'string' && + (bid.params.unit == null || (typeof bid.params.unit === 'string' && bid.params.unit.length > 0)) + ); +} + +function buildRequests(validBidRequests, bidderRequest) { + const utcOffset = (new Date()).getTimezoneOffset(); + const data = []; + validBidRequests.forEach(function(bidRequest) { + data.push(_buildServerBidRequest(bidRequest, bidderRequest, utcOffset)); + }); + const request = { + method: 'POST', + url: TARGET_URL, + data: data + }; + return request; +} + +function interpretResponse(serverResponse, request) { + const bids = []; + serverResponse.body.forEach(function(serverBid) { + bids.push(_convertServerBid(serverBid)); + }); + return bids; +} + +function _buildServerBidRequest(bidRequest, bidderRequest, utcOffset) { + return { + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + crumbs: bidRequest.crumbs, + mediaTypes: bidRequest.mediaTypes, + refererInfo: bidderRequest.refererInfo, + placementId: bidRequest.params.placementId, + utcOffset: utcOffset, + adUnitCode: bidRequest.adUnitCode, + unit: bidRequest.params.unit + } +} + +function _convertServerBid(response) { + return { + requestId: response.bidId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.ad, + ttl: response.ttl, + creativeId: response.creativeId, + netRevenue: response.netRevenue + }; +} + +registerBidder(spec); diff --git a/modules/shinezBidAdapter.md b/modules/shinezBidAdapter.md new file mode 100644 index 00000000000..e040cfbf36b --- /dev/null +++ b/modules/shinezBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Shinez Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech-team@shinez.io +``` + +# Description + +Connects to shinez.io demand sources. + +The Shinez adapter requires setup and approval from the Shinez team. +Please reach out to tech-team@shinez.io for more information. + +# Test Parameters + +```javascript +var adUnits = [{ + code: "test-div", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: "shinez", + params: { + placementId: "00654321" + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js new file mode 100644 index 00000000000..373468b2f14 --- /dev/null +++ b/modules/sirdataRtdProvider.js @@ -0,0 +1,402 @@ +/** + * This module adds Sirdata provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server + * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) + * @module modules/sirdataRtdProvider + * @requires module:modules/realTimeData + */ +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import findIndex from 'core-js-pure/features/array/find-index.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { config } from '../src/config.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'SirdataRTDModule'; + +export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + moduleConfig.params = moduleConfig.params || {}; + + var tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); + var gdprApplies = (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies ? userConsent.gdpr.gdprApplies : ''); + + moduleConfig.params.partnerId = moduleConfig.params.partnerId ? moduleConfig.params.partnerId : 1; + moduleConfig.params.key = moduleConfig.params.key ? moduleConfig.params.key : 1; + + var sirdataDomain; + var sendWithCredentials; + + if (userConsent.coppa || (userConsent.usp && (userConsent.usp[0] == '1' && (userConsent.usp[1] == 'N' || userConsent.usp[2] == 'Y')))) { + // if children or "Do not Sell" management in California, no segments, page categories only whatever TCF signal + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + gdprApplies = null; + tcString = ''; + } else if (getGlobal().getConfig('consentManagement.gdpr')) { + // Default endpoint is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + } + + // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply + if (!sirdataDomain || !gdprApplies || (utils.deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { + sirdataDomain = 'sddan.com'; + sendWithCredentials = true; + } + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer; + + const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + actualUrl : ''); + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.segments) { + addSegmentData(adUnits, data, moduleConfig, onDone); + } else { + onDone(); + } + } catch (e) { + onDone(); + utils.logError('unable to parse Sirdata data' + e); + } + } else if (req.status === 204) { + onDone(); + } + }, + error: function () { + onDone(); + utils.logError('unable to get Sirdata data'); + } + }, + null, + { + contentType: 'text/plain', + method: 'GET', + withCredentials: sendWithCredentials, + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); +} + +export function setGlobalOrtb2(segments, categories) { + try { + let addOrtb2 = {}; + let testGlobal = getGlobal().getConfig('ortb2') || {}; + if (!utils.deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { + utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + } + if (!utils.deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { + utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + } + if (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testGlobal, addOrtb2)}; + getGlobal().setConfig(ortb2); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +export function setBidderOrtb2(bidder, segments, categories) { + try { + let addOrtb2 = {}; + let testBidder = utils.deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {}; + if (!utils.deepAccess(testBidder, 'user.ext.data.sd_rtd') || !utils.deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { + utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + } + if (!utils.deepAccess(testBidder, 'site.ext.data.sd_rtd') || !utils.deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { + utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + } + if (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testBidder, addOrtb2)}; + getGlobal().setBidderConfig({ bidders: [bidder], config: ortb2 }); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +export function loadCustomFunction (todo, adUnit, list, data, bid) { + try { + if (typeof todo == 'function') { + todo(adUnit, list, data, bid); + } + } catch (e) { utils.logError(e); } + return true; +} + +export function getSegAndCatsArray(data, minScore) { + var sirdataData = {'segments': [], 'categories': []}; + minScore = minScore && typeof minScore == 'number' ? minScore : 30; + try { + if (data && data.contextual_categories) { + for (let catId in data.contextual_categories) { + let value = data.contextual_categories[catId]; + if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { + sirdataData.categories.push(catId.toString()); + } + } + } + } catch (e) { utils.logError(e); } + try { + if (data && data.segments) { + for (let segId in data.segments) { + sirdataData.segments.push(data.segments[segId].toString()); + } + } + } catch (e) { utils.logError(e); } + return sirdataData; +} + +export function addSegmentData(adUnits, data, moduleConfig, onDone) { + moduleConfig = moduleConfig || {}; + moduleConfig.params = moduleConfig.params || {}; + const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; + var sirdataData = getSegAndCatsArray(data, globalMinScore); + + if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { utils.logError('no cats'); onDone(); return adUnits; } + + const sirdataList = sirdataData.segments.concat(sirdataData.categories); + + var curationData = {'segments': [], 'categories': []}; + var curationId = '1'; + const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); + + // Global ortb2 + if (!biddersParamsExist) { + setGlobalOrtb2(sirdataData.segments, sirdataData.categories); + } + + // Google targeting + if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { + try { + // For curation Google is pid 27449 + curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + } + window.googletag.pubads().getSlots().forEach(function(n) { + if (typeof n.setTargeting !== 'undefined') { + n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + }) + } catch (e) { utils.logError(e); } + } + + // Bid targeting level for FPD non-generic biders + var bidderIndex = ''; + var indexFound = false; + + adUnits.forEach(adUnit => { + if (!biddersParamsExist && !utils.deepAccess(adUnit, 'ortb2Imp.ext.data.sd_rtd')) { + utils.deepSetValue(adUnit, 'ortb2Imp.ext.data.sd_rtd', sirdataList); + } + + adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { + bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function(i) { return i.bidder === bid.bidder; }) : false); + indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); + try { + curationData = {'segments': [], 'categories': []}; + let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore) + + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + // For curation Xandr is pid 27446 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + utils.deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'smartadserver': + case 'smart': + var target = []; + if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { + target.push(bid.params.target); + } + // For curation Smart is pid 27440 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); + } + }); + utils.deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'rubicon': + // For curation Magnite is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'ix': + var ixConfig = getGlobal().getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig) { + // For curation index is pid 27248 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + var cappIxCategories = []; + var ixLength = 0; + var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; + } + }); + getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + } + break; + + case 'proxistore': + // For curation Proxistore is pid 27484 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } else { + data.shared_taxonomy[curationId] = {contextual_categories: {}}; + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + utils.deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}}); + } + break; + + case 'criteo': + // For curation Smart is pid 27443 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, sirdataList.concat(curationData.segments).concat(curationData.categories), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'triplelift': + // For curation Triplelift is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'avct': + case 'avocet': + // For curation Avocet is pid 27522 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'smaato': + // For curation Smaato is pid 27520 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); + } else { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + default: + if (!biddersParamsExist || indexFound) { + if (!utils.deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { + utils.deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); + } + if (!utils.deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { + utils.deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + } + } + } + } + } catch (e) { utils.logError(e) } + }) + }); + + onDone(); + return adUnits; +} + +export function init(config) { + return true; +} + +export const sirdataSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getSegmentsAndCategories +}; + +submodule(MODULE_NAME, sirdataSubmodule); diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md new file mode 100644 index 00000000000..f67e34db43a --- /dev/null +++ b/modules/sirdataRtdProvider.md @@ -0,0 +1,169 @@ +# Sirdata Real-Time Data Submodule + +Module Name: Sirdata Rtd Provider +Module Type: Rtd Provider +Maintainer: bob@sirdata.com + +# Description + +Sirdata provides a disruptive API that allows its partners to leverage its +cutting-edge contextualization technology and its audience segments based on +cookies and consent or without cookies nor consent! + +User-based segments and page-level automatic contextual categories will be +attached to bid request objects sent to different SSPs in order to optimize +targeting. + +Automatic integration with Google Ad Manager and major bidders like Xandr/Appnexus, +Smartadserver, Index Exchange, Proxistore, Magnite/Rubicon or Triplelift ! + +User's country and choice management are included in the module, so it's 100% +compliant with local and regional laws like GDPR and CCPA/CPRA. + +ORTB2 compliant and FPD support for Prebid versions < 4.29 + +Contact bob@sirdata.com for information. + +### Publisher Usage + +Compile the Sirdata RTD module into your Prebid build: + +`gulp build --modules=rtdModule,sirdataRtdProvider` + +Add the Sirdata RTD provider to your Prebid config. + +Segments ids (user-centric) and category ids (page-centric) will be provided +salted and hashed : you can use them with a dedicated and private matching table. +Should you want to allow a SSP or a partner to curate your media and operate +cross-publishers campaigns with our data, please ask Sirdata (bob@sirdata.com) to +open it for you account. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + adUnitCodes: ['adUnit-1','adUnit-2'], + customFunction: overrideAppnexus, + curationId: '111', + },{ + bidder: 'ix', + sizeLimit: 1200 //specific to Index Exchange, + contextualMinRelevancyScore: 50, //Min score to filter contextual category for curation in the bidder (0-100 scale) + }] + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Sirdata Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | +| waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | +| params | Object | | Optional | +| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.setGptKeyValues | Boolean | This parameter Sirdata to set Targeting for GPT/GAM | Optional. Defaults to true. | +| params.contextualMinRelevancyScore | Integer | Min score to keep filter category in the bidders (0-100 scale). Optional. Defaults to 30. | +| params.bidders | Object | Dictionary of bidders you would like to supply Sirdata data for. | Optional. In case no bidder is specified Sirdata will atend to ad data custom and ortb2 to all bidders, adUnits & Globalconfig | + +Bidders can receive common setting : +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| bidder | String | Bidder name | Mandatory if params.bidders are specified | +| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered | +| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false | +| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at bob@sirdata.com | Optional. Default curation ids are specified for main bidders | +| contextualMinRelevancyScore | Integer | Min score to filter contextual categories for curation in the bidder (0-100 scale). Optional. Defaults to 30 or global params.contextualMinRelevancyScore if exits. | +| sizeLimit | Integer | used only for bidder 'ix' to limit the size of the get parameter in Index Exchange ad call | Optional. Default is 1000 | + + +### Overriding data sharing function +As indicated above, it is possible to provide your own bid augmentation +functions. This is useful if you know a bid adapter's API supports segment +fields which aren't specifically being added to request objects in the Prebid +bid adapter. + +Please see the following example, which provides a function to modify bids for +a bid adapter called ix and overrides the appnexus. + +data Object format for usage in this kind of function : +{ + "segments":[111111,222222], + "contextual_categories":{"333333":100}, + "shared_taxonomy":{ + "27446":{ //CurationId + "segments":[444444,555555], + "contextual_categories":{"666666":100} + } + } +} + +``` +function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) { + for (var i = 0; i < segmentsArray.length; i++) { + if (segmentsArray[i]) { + bid.params.user.segments.push(segmentsArray[i]); + } + } +} + +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + customFunction: overrideAppnexus, + curationId: '111' + },{ + bidder: 'ix', + sizeLimit: 1200, //specific to Index Exchange + customFunction: function(adUnit, segmentsArray, dataObject, bid) { + bid.params.contextual.push(dataObject.contextual_categories); + }, + }] + } + } + ] + } + ... +} +``` + +### Testing + +To view an example of available segments returned by Sirdata's backends: + +`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html` \ No newline at end of file diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 93915689cee..fbb4c14e5f8 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -5,7 +5,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const CLIENT = 'prebid_js_$prebid.version$_1.0' +const CLIENT = 'prebid_js_$prebid.version$_1.1' /** * Transform BidRequest to OpenRTB-formatted BidRequest Object @@ -98,8 +98,10 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { } }; - Object.assign(request.user, config.getConfig('fpd.user')); - Object.assign(request.site, config.getConfig('fpd.context')); + let ortb2 = config.getConfig('ortb2') || {}; + + Object.assign(request.user, ortb2.user); + Object.assign(request.site, ortb2.site); if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js index 12d5a0ae7da..de303f9e4b2 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/smartrtbBidAdapter.js @@ -17,11 +17,9 @@ function getDomain () { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['smrtb'], + aliases: ['rdigital'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null && - bid.params.medId !== null && - bid.params.zoneId !== null); + return (bid.params.pubId !== null || bid.params.zoneId !== null); }, buildRequests: function(validBidRequests, bidderRequest) { let stack = (bidderRequest.refererInfo && diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 804b25d1afc..6b34e499a99 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -64,7 +64,7 @@ export const spec = { return false; } if (!utils.getBidIdParameter('outstream_function', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); + utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header.'); return true; } } @@ -149,6 +149,7 @@ export const spec = { } const language = navigator.language ? 'language' : 'userLanguage'; + const device = { h: screen.height, w: screen.width, @@ -157,8 +158,11 @@ export const spec = { make: navigator.vendor ? navigator.vendor : '', ua: navigator.userAgent }; + const at = utils.getBidIdParameter('at', bid.params) || 2; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const requestPayload = { id: utils.generateUUID(), imp: smartxReq, @@ -176,6 +180,7 @@ export const spec = { at: at, cur: cur }; + const userExt = {}; // Add GDPR flag and consent string @@ -271,6 +276,7 @@ export const spec = { serverResponseBody.cur = pmb.currency; } }); + const bid = { requestId: currentBidRequest.bidId, currency: serverResponseBody.cur || 'USD', @@ -284,7 +290,14 @@ export const spec = { width: smartxBid.w, height: smartxBid.h }; + + bid.meta = bid.meta || {}; + if (smartxBid && smartxBid.adomain && smartxBid.adomain.length > 0) { + bid.meta.advertiserDomains = smartxBid.adomain; + } + const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + if (context === 'outstream') { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ @@ -311,7 +324,7 @@ export const spec = { return utils.logMessage('SmartX outstream video loaded event'); }, ended: function ended() { - utils.logMessage('SmartX outstream renderer video event'); + return utils.logMessage('SmartX outstream renderer video event'); } }); } catch (err) { @@ -328,50 +341,25 @@ export const spec = { } function createOutstreamScript(bid) { - // for SmartPlay 4.12 - function scPrebidClose(ele, completeCollapsed) { - if (completeCollapsed) { - document.getElementById(ele.id).style.display = 'none'; - } - } - const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; - const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; - const confHeaderText = utils.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; - const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; - const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; + const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); + const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options); + const confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); + const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); + const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; - // for SmartPlay 4.12 - let initCollapsed = true; - let completeCollapsed = true; - if (confStartOpen === 'true') { - initCollapsed = false; - } - if (confEndingScreen === 'true') { - completeCollapsed = false; - } - utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - let smartPlayObj = { + var smartPlayObj = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - headerText: confHeaderText, + title: confTitle, skipOffset: confSkipOffset, - behaviourMatrix: { - init: { - 'collapsed': initCollapsed - }, - complete: { - 'collapsed': completeCollapsed - } - }, - environmentVars: { - desiredBitrate: confDesiredBitrate, - }, + startOpen: confStartOpen, + endingScreen: confEndingScreen, + desiredBitrate: confDesiredBitrate, onStartCallback: function (m, n) { try { window.sc_smartIntxtStart(n); @@ -384,23 +372,24 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { - scPrebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, }; + smartPlayObj.adResponse = bid.vastContent; - const script = window.document.createElement('script'); + + const divID = '[id="' + elementId + '"]'; + var script = document.createElement('script'); + script.src = 'https://dco.smartclip.net/?plc=7777778'; script.type = 'text/javascript'; script.async = 'true'; - script.src = 'https://dco.smartclip.net/?plc=7777777'; script.onload = script.onreadystatechange = function () { - var rs = this.readyState; - if (rs && rs != 'complete' && rs != 'loaded') return; try { - window.SmartPlay(elementId, smartPlayObj); + // eslint-disable-next-line + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); } catch (e) { - utils.logError('error caught : ' + e); + utils.logError('[SmartPlay][renderer] Error caught: ' + e); } }; return script; diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 082a36f3dde..223e51763b9 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -36,11 +36,11 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', - desiredBitrate: 1600, + startOpen: true, + endingScreen: true, + desiredBitrate: 800, }, } }], @@ -71,11 +71,11 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', - desiredBitrate: 1600, + startOpen: true, + endingScreen: true, + desiredBitrate: 800, }, user: { data: [{ diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index ff60d08e48b..610617155ed 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'smartyads'; @@ -49,6 +50,7 @@ export const spec = { 'secure': 1, 'host': location.host, 'page': location.pathname, + 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f965310abdd..fb05298a230 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -108,16 +108,31 @@ export const spec = { * @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 - }); + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; } } + + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + const syncs = [] + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://csync.smilewanted.com' + params + }); + } + return syncs; } } diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index a55992cec22..e5bd76eba9f 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,10 +1,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; - const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -117,7 +116,18 @@ export const spec = { payload.schain = JSON.stringify(validBidRequests[0].schain) } if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - payload.userid = JSON.stringify(validBidRequests[0].userId); + const userIds = deepClone(validBidRequests[0].userId); + + if (userIds.id5id) { + userIds.id5id = deepAccess(userIds, 'id5id.uid'); + } + + payload.userid = JSON.stringify(userIds); + } + + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + payload.eids = JSON.stringify(eids); } let keywords = validBidRequests[0].params.keywords; // a CSV of keywords @@ -130,6 +140,12 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + if (config.getConfig('coppa') === true) { + payload.coppa = 1; + } else { + payload.coppa = 0; + } + // If there is no key_maker data, then don't make the request. if (isEmpty(data)) { return null; diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js index f5d3a6b1bb5..6989abff143 100644 --- a/modules/sortableBidAdapter.js +++ b/modules/sortableBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'sortable'; const SERVER_URL = 'https://c.deployads.com'; @@ -218,6 +219,8 @@ export const spec = { return rv; }); const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const bidUserId = validBidReqs[0].userId; + const eids = createEidsArray(bidUserId); const sortableBidReq = { id: utils.getUniqueIdentifierStr(), imp: sortableImps, @@ -241,6 +244,9 @@ export const spec = { h: screen.height }, }, + user: { + ext: {} + } }; if (bidderRequest && bidderRequest.timeout > 0) { sortableBidReq.tmax = bidderRequest.timeout; @@ -255,6 +261,9 @@ export const spec = { sortableBidReq.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 } } + if (eids.length) { + sortableBidReq.user.ext.eids = eids; + } if (bidderRequest.uspConsent) { sortableBidReq.regs.ext.us_privacy = bidderRequest.uspConsent; } diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 9ec4d339753..708cb110dbd 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' import { createEidsArray } from './userId/eids.js'; +import {config} from '../src/config.js'; export const spec = { code: 'sovrn', @@ -72,30 +73,29 @@ export const spec = { bidfloor: floorInfo.floor } - const segmentsString = utils.getBidIdParameter('segments', bid.params) + imp.ext = utils.getBidIdParameter('ext', bid.ortb2Imp) || undefined + const segmentsString = utils.getBidIdParameter('segments', bid.params) if (segmentsString) { - imp.ext = { - deals: segmentsString.split(',').map(deal => deal.trim()) - } + imp.ext = imp.ext || {} + imp.ext.deals = segmentsString.split(',').map(deal => deal.trim()) } + sovrnImps.push(imp) + }) - sovrnImps.push(imp); - }); - - const page = bidderRequest.refererInfo.referer + const fpd = config.getConfig('ortb2') || {} + const site = fpd.site || {} + site.page = bidderRequest.refererInfo.referer // clever trick to get the domain - const domain = utils.parseUrl(page).hostname + site.domain = utils.parseUrl(site.page).hostname const sovrnBidReq = { id: utils.getUniqueIdentifierStr(), imp: sovrnImps, - site: { - page, - domain - } - }; + site: site, + user: fpd.user || {} + } if (schain) { sovrnBidReq.source = { diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index c85a836435e..b60d25db4d6 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -215,6 +215,12 @@ export const spec = { } }; + // If the publisher asks to ignore the bidder cache key we need to return the full vast xml + // so that it can be cached on the publishes specified server. + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + requestPayload['ext']['wrap_response'] = 0; + } + if (utils.getBidIdParameter('number_of_ads', bid.params)) { requestPayload['ext']['number_of_ads'] = utils.getBidIdParameter('number_of_ads', bid.params); } @@ -285,9 +291,10 @@ export const spec = { if (!utils.isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } + const urlQueryParams = 'src_sys=prebid' return { method: 'POST', - url: URL + channelId, + url: URL + channelId + '?' + urlQueryParams, data: requestPayload, bidRequest: bidderRequest }; @@ -336,14 +343,19 @@ export const spec = { ttl: 360, netRevenue: true, channel_id: serverResponseBody.id, - cache_key: spotxBid.ext.cache_key, - vastUrl: 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key, - videoCacheKey: spotxBid.ext.cache_key, mediaType: VIDEO, width: spotxBid.w, height: spotxBid.h }; + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + bid.vastXml = spotxBid.adm; + } else { + bid.cache_key = spotxBid.ext.cache_key; + bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + bid.videoCacheKey = spotxBid.ext.cache_key; + } + bid.meta = bid.meta || {}; if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { bid.meta.advertiserDomains = spotxBid.adomain; @@ -355,7 +367,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '//', + url: '/', config: { adText: 'SpotX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index b6e5b88e0f4..41191b3123a 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -2,14 +2,13 @@ import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import strIncludes from 'core-js-pure/features/string/includes.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const TMAX = 450; -const BIDDER_VERSION = '4.7'; +const BIDDER_VERSION = '4.8'; const W = window; const { navigator } = W; const oneCodeDetection = {}; @@ -151,11 +150,13 @@ function mapBanner(slot) { } function mapImpression(slot) { + const { adUnitCode, bidId, params } = slot; + const { id, siteId } = params || {}; const imp = { - id: (slot.params && slot.params.id) ? slot.params.id : 'bidid-' + slot.bidId, + id: id && siteId ? id : 'bidid-' + bidId, banner: mapBanner(slot), /* native: mapNative(slot), */ - tagid: slot.adUnitCode, + tagid: adUnitCode, }; const bidfloor = (slot.params && slot.params.bidFloor) ? parseFloat(slot.params.bidFloor) : undefined; @@ -255,6 +256,7 @@ const spec = { } const siteId = setOnAny(validBidRequests, 'params.siteId'); + const publisherId = setOnAny(validBidRequests, 'params.publisherId'); const page = setOnAny(validBidRequests, 'params.page') || bidderRequest.refererInfo.referer; const domain = setOnAny(validBidRequests, 'params.domain') || utils.parseUrl(page).hostname; const tmax = setOnAny(validBidRequests, 'params.tmax') ? parseInt(setOnAny(validBidRequests, 'params.tmax'), 10) : TMAX; @@ -270,7 +272,13 @@ const spec = { const payload = { id: bidderRequest.auctionId, - site: { id: siteId, page, domain, ref }, + site: { + id: siteId, + publisher: publisherId ? { id: publisherId } : undefined, + page, + domain, + ref + }, imp: validBidRequests.map(slot => mapImpression(slot)), tmax, user: {}, @@ -290,6 +298,7 @@ const spec = { }, interpretResponse(serverResponse, request) { + const { bidderRequest } = request; const response = serverResponse.body; const bids = []; const site = JSON.parse(request.data).site; // get page and referer data from request @@ -297,50 +306,68 @@ const spec = { let seat; if (response.seatbid !== undefined) { + /* + Match response to request, by comparing bid id's + 'bidid-' prefix indicates oneCode (parameterless) request and response + */ response.seatbid.forEach(seatbid => { seat = seatbid.seat; seatbid.bid.forEach(serverBid => { - const bidRequest = request.bidderRequest.bids.filter(b => { - const bidId = b.params ? b.params.id : 'bidid-' + b.bidId; - return bidId === serverBid.impid; + // get data from bid response + const { adomain, crid, impid, exp, ext, price, w, h } = serverBid; + + const bidRequest = bidderRequest.bids.filter(b => { + const { bidId, params } = b; + const { id, siteId } = params || {}; + const currentBidId = id && siteId ? id : 'bidid-' + bidId; + return currentBidId === impid; })[0]; - site.slot = bidRequest && bidRequest.params ? bidRequest.params.slotid : undefined; - if (serverBid.ext) { + // get data from linked bidRequest + const { bidId, params } = bidRequest || {}; + + // get slot id for current bid + site.slot = params && params.id; + + if (ext) { /* bid response might include ext object containing siteId / slotId, as detected by OneCode update site / slot data in this case */ - site.id = serverBid.ext.siteid || site.id; - site.slot = serverBid.ext.slotid || site.slot; + const { siteid, slotid } = ext; + site.id = siteid || site.id; + site.slot = slotid || site.slot; } - if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { - // store site data for future notification - oneCodeDetection[bidRequest.bidId] = [site.id, site.slot]; + if (bidRequest && site.id && !site.id.includes('bidid')) { + // found a matching request; add this bid - const bidFloor = (bidRequest.params && bidRequest.params.bidFloor) ? bidRequest.params.bidFloor : 0; + // store site data for future notification + oneCodeDetection[bidId] = [site.id, site.slot]; const bid = { - requestId: bidRequest.bidId, - creativeId: serverBid.crid || 'mcad_' + request.bidderRequest.auctionId + '_' + request.bidderRequest.params.id, - cpm: serverBid.price, + requestId: bidId, + creativeId: crid || 'mcad_' + bidderRequest.auctionId + '_' + site.slot, + cpm: price, currency: response.cur, - ttl: serverBid.exp || 300, - width: serverBid.w, - height: serverBid.h, + ttl: exp || 300, + width: w, + height: h, bidderCode: BIDDER_CODE, mediaType: 'banner', meta: { - advertiserDomains: serverBid.adomain, + advertiserDomains: adomain, networkName: seat, }, netRevenue: true, - ad: renderCreative(site, response.id, serverBid, seat, request.bidderRequest), + ad: renderCreative(site, response.id, serverBid, seat, bidderRequest), }; if (bid.cpm > 0) { - if (bid.cpm >= bidFloor) { + // check bidFloor (if present in params) + const { bidFloor } = params || {}; + + if (!bidFloor || bid.cpm >= bidFloor) { bids.push(bid); } else { utils.logWarn('Discarding bid due to bidFloor setting', bid.cpm, bidFloor); diff --git a/modules/sspBCBidAdapter.md b/modules/sspBCBidAdapter.md index 67a2ba1c7ba..f22e8e6c458 100644 --- a/modules/sspBCBidAdapter.md +++ b/modules/sspBCBidAdapter.md @@ -16,10 +16,12 @@ Required parameters: Optional parameters: - site id - adslot id +- publisher id - domain - page - tmax - bidFloor +- test # Test Parameters ``` @@ -29,18 +31,15 @@ var adUnits = [ code: 'banner-div', mediaTypes: { banner: { - sizes: [[300, 250], [300,600]] + sizes: [[300, 250]] } }, bids: [{ bidder: 'sspBC', params: { - id: '006', // optional - siteId: '235911', // optional - domain: 'somesite.pl', // optional - page: 'somesite.pl/somepage.html', // optional - tmax: 250, // optional - bidFloor: 0.1 // optional + id: "006", + siteId: "235911", + test: 1 } }] } diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index 7a573ca4c4b..41fdb72e76e 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -9,7 +9,23 @@ const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_TTL = 600; const SUBLIME_ANTENNA = 'antenna.ayads.co'; -const SUBLIME_VERSION = '0.7.0'; +const SUBLIME_VERSION = '0.7.1'; + +/** + * Identify the current device type + * @returns {string} + */ +function detectDevice() { + const isMobile = /(?:phone|windowss+phone|ipod|blackberry|Galaxy Nexus|SM-G892A|(?:android|bbd+|meego|silk|googlebot) .+?mobile|palm|windowss+ce|opera mini|avantgo|docomo)/i; + + const isTablet = /(?:ipad|playbook|Tablet|(?:android|bb\\d+|meego|silk)(?! .+? mobile))/i; + + return ( + (isMobile.test(navigator.userAgent) && 'm') || // mobile + (isTablet.test(navigator.userAgent) && 't') || // tablet + 'd' // desktop + ); +} /** * Debug log message @@ -39,8 +55,9 @@ export function setState(value) { /** * Send pixel to our debug endpoint * @param {string} eventName - Event name that will be send in the e= query string + * @param {string} [sspName] - The optionnal name of the AD provider */ -export function sendEvent(eventName) { +export function sendEvent(eventName, sspName) { const ts = Date.now(); const eventObject = { t: ts, @@ -51,8 +68,15 @@ export function sendEvent(eventName) { puid: state.transactionId || state.notifyId, trId: state.transactionId || state.notifyId, pbav: SUBLIME_VERSION, + pubtimeout: config.getConfig('bidderTimeout'), + pubpbv: '$prebid.version$', + device: detectDevice(), }; + if (eventName === 'bidwon') { + eventObject.sspname = sspName || ''; + } + log('Sending pixel for event: ' + eventName, eventObject); const queryString = utils.formatQS(eventObject); @@ -179,7 +203,8 @@ function interpretResponse(serverResponse, bidRequest) { netRevenue: response.netRevenue || true, ttl: response.ttl || DEFAULT_TTL, ad: response.ad, - pbav: SUBLIME_VERSION + pbav: SUBLIME_VERSION, + sspname: response.sspname || null }; bidResponses.push(bidResponse); @@ -194,7 +219,7 @@ function interpretResponse(serverResponse, bidRequest) { */ function onBidWon(bid) { log('Bid won', bid); - sendEvent('bidwon'); + sendEvent('bidwon', bid.sspname); } /** diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 55a91d87448..aaff637c790 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -157,6 +157,9 @@ export const spec = { pos }; if (bid.mediaTypes.video) { + if (!bid.params.video) { + bid.params.video = {}; + } this.setValidVideoParams(bid.mediaTypes.video, bid.params.video); } if (bid.params.video) { @@ -182,7 +185,6 @@ export const spec = { logWarn('Synacormedia: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return; } - const {id, seatbid: seatbids} = serverResponse.body; let bids = []; if (id && seatbids) { @@ -219,7 +221,6 @@ export const spec = { } const bidObj = { requestId: impid, - adId: bid.id.replace(/~/g, '-'), cpm: parseFloat(bid.price), width: parseInt(width, 10), height: parseInt(height, 10), @@ -230,6 +231,11 @@ export const spec = { ad: creative, ttl: 60 }; + + if (bid.adomain != undefined || bid.adomain != null) { + bidObj.meta = { advertiserDomains: bid.adomain }; + } + if (isVideo) { const [, uuid] = nurl.match(/ID=([^&]*)&?/); if (!config.getConfig('cache.url')) { diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 7782f151802..1cf5a0f8be7 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -2,18 +2,22 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'tappx'; const TTL = 360; const CUR = 'USD'; +const TAPPX_BIDDER_VERSION = '0.1.10413'; +const TYPE_CNN = 'prebidjs'; +const VIDEO_SUPPORT = ['instream']; + var HOST; var hostDomain; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -22,11 +26,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - if ((bid.params == null) || (bid.params.endpoint == null) || (bid.params.tappxkey == null)) { - utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters. ${JSON.stringify(bid)}`); - return false; - } - return true; + return validBasic(bid) && validMediaType(bid) }, /** @@ -61,7 +61,7 @@ export const spec = { const bids = []; responseBody.seatbid.forEach(serverSeatBid => { serverSeatBid.bid.forEach(serverBid => { - bids.push(interpretBannerBid(serverBid, originalRequest)); + bids.push(interpretBid(serverBid, originalRequest)); }); }); @@ -104,25 +104,66 @@ export const spec = { } } +function validBasic(bid) { + if ( + (bid.params == null) || + (bid.params.endpoint == null) || + (bid.params.tappxkey == null)) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters.`); + return false; + } + return true; +} + +function validMediaType(bid) { + const video = utils.deepAccess(bid, 'mediaTypes.video'); + + // Video validations + if (typeof video != 'undefined') { + if (VIDEO_SUPPORT.indexOf(video.context) === -1) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters for Video. Only "instream" is suported.`); + return false; + } + } + + return true; +} + /** * Parse the response and generate one bid object. * * @param {object} serverBid Bid by OpenRTB 2.5 * @returns {object} Prebid banner bidObject */ -function interpretBannerBid(serverBid, request) { - return { +function interpretBid(serverBid, request) { + let bidReturned = { requestId: request.bids.bidId, cpm: serverBid.price, currency: serverBid.cur ? serverBid.cur : CUR, width: serverBid.w, height: serverBid.h, - ad: serverBid.adm, ttl: TTL, creativeId: serverBid.crid, netRevenue: true, - mediaType: BANNER, } + + if (typeof serverBid.dealId != 'undefined') { bidReturned.dealId = serverBid.dealId } + + if (typeof request.bids.mediaTypes != 'undefined' && typeof request.bids.mediaTypes.video != 'undefined') { + bidReturned.vastXml = serverBid.adm; + bidReturned.vastUrl = serverBid.lurl; + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = VIDEO; + } else { + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = BANNER; + } + + if (typeof bidReturned.adomain != 'undefined' || bidReturned.adomain != null) { + bidReturned.meta = { advertiserDomains: request.bids.adomain }; + } + + return bidReturned; } /** @@ -134,12 +175,15 @@ function interpretBannerBid(serverBid, request) { */ function buildOneRequest(validBidRequests, bidderRequest) { HOST = utils.deepAccess(validBidRequests, 'params.host'); - hostDomain = HOST.split('/', 1)[0]; + let hostInfo = getHostInfo(HOST); + hostDomain = hostInfo.domain; const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); + const BIDEXTRA = utils.deepAccess(validBidRequests, 'params.ext'); const bannerMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.video'); const { refererInfo } = bidderRequest; // let requests = []; @@ -201,11 +245,32 @@ function buildOneRequest(validBidRequests, bidderRequest) { imp.banner = banner; } + if (videoMediaType) { + let video = {}; + w = videoMediaType.playerSize[0][0]; + h = videoMediaType.playerSize[0][1]; + video.w = w; + video.h = h; + + video.mimes = videoMediaType.mimes; + + imp.video = video; + } + imp.id = validBidRequests.bidId; imp.tagid = tagid; imp.secure = 1; imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + + let bidder = {}; + bidder.tappxkey = TAPPXKEY; + bidder.endpoint = ENDPOINT; + bidder.host = hostInfo.url; + bidder.ext = (typeof BIDEXTRA == 'object') ? BIDEXTRA : undefined; + + imp.ext = {}; + imp.ext.bidder = bidder; // < Imp object // > Device object @@ -230,8 +295,6 @@ function buildOneRequest(validBidRequests, bidderRequest) { // > Params let params = {}; params.host = 'tappx.com'; - params.tappxkey = TAPPXKEY; - params.endpoint = ENDPOINT; params.bidfloor = BIDFLOOR; // < Params @@ -253,6 +316,14 @@ function buildOneRequest(validBidRequests, bidderRequest) { if (config.getConfig('coppa') === true) { regs.coppa = config.getConfig('coppa') === true ? 1 : 0; } + + // Universal ID + const eidsArr = utils.deepAccess(validBidRequests, 'userIdAsEids'); + payload.user = { + ext: { + eids: eidsArr + } + }; // < GDPR // > Payload @@ -267,10 +338,12 @@ function buildOneRequest(validBidRequests, bidderRequest) { payload.params = params; payload.regs = regs; // < Payload - + utils.logMessage('---------------------'); + utils.logMessage(JSON.stringify(payload)); + utils.logMessage('---------------------'); return { method: 'POST', - url: `https://${HOST}/${ENDPOINT}?type_cnn=prebidjs`, + url: `https://${HOST}/${ENDPOINT}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}`, data: JSON.stringify(payload), bids: validBidRequests }; @@ -286,4 +359,26 @@ function getOs() { if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } } +function getHostInfo(hostParam) { + let domainInfo = {}; + + domainInfo.domain = hostParam.split('/', 1)[0]; + domainInfo.url = hostParam; + + let regexNewEndpoints = new RegExp(`^(zz.*|testing)\.ssp\.tappx\.com$`, 'i'); + let regexClassicEndpoints = new RegExp(`^[a-z]{3}\.[a-z]{3}\.tappx\.com$`, 'i'); + + if (regexNewEndpoints.test(domainInfo.domain)) { + let endpoint = domainInfo.domain.split('.', 1)[0] + if (endpoint.toUpperCase().indexOf('TESTING') === -1) { + domainInfo.endpoint = endpoint + domainInfo.new_endpoint = true; + } + } else if (regexClassicEndpoints.test(domainInfo.domain)) { + domainInfo.new_endpoint = false; + } + + return domainInfo; +} + registerBidder(spec); diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index d9ffd98b6c5..a07bb2d88d1 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: prebid@tappx.com # Description Module that connects to :tappx demand sources. +Suppots Banner and Instream Video. Please use ```tappx``` as the bidder code. Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] @@ -35,3 +36,35 @@ Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] } ]; ``` + + +# Video Test Parameters +``` + var adUnits = [ + { + code: 'video-ad-div', + renderer: { + options: { + text: "Tappx instream Video" + } + }, + mediaTypes: { + video: { + context: "instream", + mimes : [ "video/mp4", "application/javascript" ], + playerSize: [320, 250] + } + }, + bids: [{ + bidder: 'tappx', + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-desktop-1234", + endpoint: "VZ12TESTCTV", + bidfloor: 0.005, + test: true + } + }] + } + ]; +``` diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index aad7f6762c4..32be7e62bbd 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -99,6 +99,9 @@ export const spec = { currency: bid.currency, netRevenue: true, ttl: bid.ttl, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, ad: bid.ad, requestId: bid.bidId, creativeId: bid.creativeId, diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 71651b5af94..b2904045144 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -276,7 +276,6 @@ function createBid(status, reqBid, response, width, height, bidderCode) { width: width, height: height, bidderCode: bidderCode, - adId: response.id, currency: 'USD', netRevenue: true, ttl: 300, diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index b681b0980ea..e8d248eea03 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -122,8 +122,8 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; - if (!utils.isEmpty(bidRequest.fpd)) { - imp.fpd = _getAdUnitFpd(bidRequest.fpd); + if (!utils.isEmpty(bidRequest.ortb2Imp)) { + imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); } return imp; }); @@ -190,9 +190,10 @@ function _getGlobalFpd() { const fpd = {}; const context = {} const user = {}; + const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; - const fpdContext = Object.assign({}, config.getConfig('fpd.context')); - const fpdUser = Object.assign({}, config.getConfig('fpd.user')); + const fpdContext = Object.assign({}, ortbData.context); + const fpdUser = Object.assign({}, ortbData.user); _addEntries(context, fpdContext); _addEntries(user, fpdUser); @@ -210,7 +211,7 @@ function _getAdUnitFpd(adUnitFpd) { const fpd = {}; const context = {}; - _addEntries(context, adUnitFpd.context); + _addEntries(context, adUnitFpd.ext); if (!utils.isEmpty(context)) { fpd.context = context; diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index a86ac1f2874..3116400edf7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -2,9 +2,11 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; +const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -40,98 +42,30 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; const bids = validBidRequests || []; - let priceType = 'net'; - let pageKeywords; - let reqId; - + const newFormatBids = []; + const oldFormatBids = []; + const requests = []; bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; - } - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + if (bid.params.useNewFormat) { + newFormatBids.push(bid); } else { - slotsMap[adUnitCode].bids.push(bid); + oldFormatBids.push(bid); } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; + if (newFormatBids.length) { + const newFormatRequests = newFormatRequest(newFormatBids, bidderRequest); + if (newFormatRequests) { + requests.push(newFormatRequests); } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + } + if (oldFormatBids.length) { + const oldFormatRequests = oldFormatRequest(oldFormatBids, bidderRequest); + if (oldFormatRequests) { + requests.push(oldFormatRequests); } } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -143,8 +77,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; let errorMessage; @@ -155,7 +87,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -192,66 +124,75 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { +function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'USD', - netRevenue: priceType !== 'gross', - ttl: TIME_TO_LIVE, - 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; + const { bidsMap, priceType, newFormat } = bidRequest; + let bid; + let slot; + if (newFormat) { + bid = bidsMap[serverBid.impid]; + } else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + slot = awaitingBids[sizeId][0]; + bid = slot.bids.shift(); } + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); + if (!errorMessage && bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: newFormat ? false : priceType !== 'gross', + ttl: TIME_TO_LIVE, + 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; } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + + bidResponses.push(bidResponse); + } + + if (slot && !slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); } } if (errorMessage) { @@ -284,4 +225,343 @@ function createRenderer (bid, rendererParams, RendererConst) { return rendererInst; } +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + if (protocols && protocols.length) { + result.protocols = protocols; + } + + return result; +} + +function createBannerRequest(bid, mediaType) { + const sizes = mediaType.sizes || bid.sizes; + if (!sizes || !sizes.length) return; + + let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); + let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + + if (format.length) { + result.format = format + } + return result; +} + +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bid.params.bidFloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: bid.sizes.map(([w, h]) => ({w, h})) + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function newFormatRequest(validBidRequests, bidderRequest) { + if (!validBidRequests.length) { + return null; + } + let pageKeywords = null; + let jwpseg = null; + let content = null; + let schain = null; + let userId = null; + let userIdAsEids = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + const bidFloor = _getFloor(mediaTypes || {}, bid); + const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; + if (jwTargeting) { + if (!jwpseg && jwTargeting.segments) { + jwpseg = jwTargeting.segments; + } + if (!content && jwTargeting.content) { + content = jwTargeting.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + } + }; + + if (bidFloor) { + impObj.bidfloor = bidFloor; + } + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } + }); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; + } + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; + } + request.regs.ext.us_privacy = uspConsent; + } + + return { + method: 'POST', + url: NEW_ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; +} + +function oldFormatRequest(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let pageKeywords; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + pageKeywords = keywords; + } + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const payload = { + pt: priceType, + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap, + priceType + }; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index a72f1ba85aa..e891df8f161 100644 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -52,6 +52,18 @@ TrustX Bid Adapter supports Banner and Video (instream and outstream). } } ] + },{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "trustx", + params: { + uid: '58851', + useNewFormat: true + } + } + ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 4003c6ed5fd..9b9134a8ef0 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -331,6 +331,11 @@ function addUserId(bidData, userId) { bidData[userIdProviderKey + '_linkType'] = userIdObjectOrValue.ext.linkType; } break; + case 'uid2': + if (userIdObjectOrValue.id) { + bidData['eids'] = userIdProviderKey + ',' + userIdObjectOrValue.id + } + break; default: bidData[userIdProviderKey] = userIdObjectOrValue; break; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js new file mode 100644 index 00000000000..053b57cb76d --- /dev/null +++ b/modules/uid2IdSystem.js @@ -0,0 +1,97 @@ +/** + * This module adds uid2 ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/uid2IdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'uid2'; +const GVLID = 887; +const LOG_PRE_FIX = 'UID2: '; +const ADVERTISING_COOKIE = '__uid2_advertising_token'; + +function readCookie() { + return storage.cookiesAreEnabled() ? storage.getCookie(ADVERTISING_COOKIE) : null; +} + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ADVERTISING_COOKIE) : null; +} + +function getStorage() { + return getStorageManager(GVLID, MODULE_NAME); +} + +const storage = getStorage(); + +const logInfo = createLogInfo(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + if (value) { + const bidIds = { + id: value + } + result.uid2 = bidIds; + logInfo('Decoded value ' + JSON.stringify(result)); + return result; + } + return undefined; +} + +/** @type {Submodule} */ +export const uid2IdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * Vendor id of Prebid + * @type {Number} + */ + gvlid: GVLID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{uid2:{ id: string }} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData + * @returns {uid2Id} + */ + getId(config, consentData) { + logInfo('Creating UID 2.0'); + let value = readCookie() || readFromLocalStorage(); + logInfo('The advertising token: ' + value); + return {id: value} + }, + +}; + +// Register submodule for userId +submodule('userId', uid2IdSubmodule); diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md new file mode 100644 index 00000000000..59149e562ac --- /dev/null +++ b/modules/uid2IdSystem.md @@ -0,0 +1,24 @@ +## UID 2.0 User ID Submodule + +UID 2.0 ID Module. + +### Prebid Params + +Individual params may be set for the UID 2.0 Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'uid2' + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the UID 2.0 User ID Module integration. + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the UID20 module - `"uid2"` | `"uid2"` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the UID 2.O ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"uid2": { "id": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}}` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 80750ccaae8..93d2ead15ea 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -71,7 +71,7 @@ const USER_IDS_CONFIG = { // identityLink 'idl_env': { source: 'liveramp.com', - atype: 1 + atype: 3 }, // liveIntentId @@ -80,7 +80,7 @@ const USER_IDS_CONFIG = { return data.lipbid; }, source: 'liveintent.com', - atype: 1, + atype: 3, getEidExt: function(data) { if (Array.isArray(data.segments) && data.segments.length) { return { @@ -93,7 +93,7 @@ const USER_IDS_CONFIG = { // britepoolId 'britepoolid': { source: 'britepool.com', - atype: 1 + atype: 3 }, // lotamePanoramaId @@ -111,7 +111,15 @@ const USER_IDS_CONFIG = { // merkleId 'merkleId': { source: 'merkleinc.com', - atype: 1 + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + return (data && data.keyID) ? { + keyID: data.keyID + } : undefined; + } }, // NetId @@ -152,6 +160,12 @@ const USER_IDS_CONFIG = { atype: 1 }, + // nextroll + 'nextrollId': { + source: 'nextroll.com', + atype: 1 + }, + // IDx 'idx': { source: 'idx.lat', @@ -161,7 +175,7 @@ const USER_IDS_CONFIG = { // Verizon Media ConnectID 'connectid': { source: 'verizonmedia.com', - atype: 1 + atype: 3 }, // Neustar Fabrick @@ -169,9 +183,38 @@ const USER_IDS_CONFIG = { source: 'neustar.biz', atype: 1 }, + // MediaWallah OpenLink + 'mwOpenLinkId': { + source: 'mediawallahscript.com', + atype: 1 + }, 'tapadId': { source: 'tapad.com', atype: 1 + }, + // Novatiq Snowflake + 'novatiq': { + getValue: function(data) { + return data.snowflake + }, + source: 'novatiq.com', + atype: 1 + }, + 'uid2': { + source: 'uidapi.com', + atype: 3, + getValue: function(data) { + return data.id; + } + }, + 'deepintentId': { + source: 'deepintent.com', + atype: 3 + }, + // Admixer Id + 'admixerId': { + source: 'admixer.net', + atype: 3 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index b69c4b9bd5e..a00aedcc52e 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -119,6 +119,14 @@ userIdAsEids = [ }] }, + { + source: 'nextroll.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'audigent.com', uids: [{ @@ -142,12 +150,47 @@ userIdAsEids = [ atype: 1 }] }, + { + source: 'mediawallahscript.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'tapad.com', uids: [{ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'novatiq.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'uidapi.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'admixer.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'deepintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] } ] ``` diff --git a/modules/userId/index.js b/modules/userId/index.js index 3253be42a76..8c48dcf9c7b 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -101,6 +101,7 @@ * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs * @property {(string|undefined)} emailHash - if provided, the hashed email address of a user + * @property {(string|undefined)} notUse3P - use to retrieve envelope from 3p endpoint */ /** @@ -247,7 +248,7 @@ function getStoredValue(storage, key = undefined) { } } // support storing a string or a stringified object - if (typeof storedValue === 'string' && storedValue.charAt(0) === '{') { + if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); } } catch (e) { @@ -754,7 +755,8 @@ function updateSubmodules() { // find submodule and the matching configuration, if found create and append a SubmoduleContainer submodules = addedSubmodules.map(i => { - const submoduleConfig = find(configs, j => j.name === i.name); + const submoduleConfig = find(configs, j => j.name && j.name.toLowerCase() === i.name.toLowerCase()); + if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name; i.findRootDomain = findRootDomain; return submoduleConfig ? { submodule: i, diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 267b3a60cea..aef50eeccdf 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -43,7 +43,8 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999', // Set your real identityLink placement ID here + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'cookie', @@ -77,6 +78,41 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: 'mwOpenLinkId', + params: { + accountId: 0000, + partnerId: 0000, + uid: '12345xyz' + } + },{ + name: "merkleId", + params: { + vendor:'sdfg', + sv_cid:'dfg', + sv_pubid:'xcv', + sv_domain:'zxv' + }, + storage: { + type: "cookie", + name: "merkleId", + expires: 30 + } + },{ + name: 'uid2' + } + }, { + name: 'admixerId', + params: { + pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id + e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256) + p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256) + }, + storage: { + type: 'cookie', + name: '__adm__admixer', + expires: 30 + } }], syncDelay: 5000, auctionDelay: 1000 @@ -109,7 +145,8 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999', // Set your real identityLink placement ID here + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'html5', @@ -148,13 +185,57 @@ pbjs.setConfig({ expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, + }, { + name: 'nextrollId', + params: { + partnerId: "1009", // Set your real NextRoll partner ID here for production + } }, { name: 'criteo', storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible type: 'html5', name: '_criteoId', expires: 1 + } + },{ + name: "merkleId", + params: { + vendor:'sdfg', + sv_cid:'dfg', + sv_pubid:'xcv', + sv_domain:'zxv' + }, + storage: { + type: "html5", + name: "merkleId", + expires: 30 + } + }, { + name: 'admixerId', + params: { + pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id + e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256) + p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256) + }, + storage: { + type: 'html5', + name: 'admixerId', + expires: 30 } + },{ + name: "deepintentId", + storage: { + type: "html5", + name: "_dpes_id", + expires: 90 + } + },{ + name: "deepintentId", + storage: { + type: "cookie", + name: "_dpes_id", + expires: 90 + } }], syncDelay: 5000 } @@ -182,6 +263,10 @@ pbjs.setConfig({ { name: "criteo", value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } + }, + { + name: "novatiq", + value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } }], syncDelay: 5000 } diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 725482d07c3..a5829b9cd9c 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -2,10 +2,14 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'visx'; -const ENDPOINT_URL = 'https://t.visx.net/hb'; +const BASE_URL = 'https://t.visx.net'; +const ENDPOINT_URL = BASE_URL + '/hb'; const TIME_TO_LIVE = 360; const DEFAULT_CUR = 'EUR'; -const ADAPTER_SYNC_URL = 'https://t.visx.net/push_sync'; +const ADAPTER_SYNC_URL = BASE_URL + '/push_sync'; +const TRACK_WIN_URL = BASE_URL + '/track/win'; +const TRACK_PENDING_URL = BASE_URL + '/track/pending'; +const TRACK_TIMEOUT_URL = BASE_URL + '/track/bid_timeout'; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -170,6 +174,18 @@ export const spec = { url: ADAPTER_SYNC_URL + (query.length ? '?' + query.join('&') : '') }]; } + }, + onSetTargeting: function(bid) { + // Call '/track/pending' with the corresponding bid.requestId + utils.triggerPixel(TRACK_PENDING_URL + '?requestId=' + bid.requestId); + }, + onBidWon: function(bid) { + // Call '/track/win' with the corresponding bid.requestId + utils.triggerPixel(TRACK_WIN_URL + '?requestId=' + bid.requestId); + }, + onTimeout: function(timeoutData) { + // Call '/track/bid_timeout' with timeout data + utils.triggerPixel(TRACK_TIMEOUT_URL + '?data=' + JSON.stringify(timeoutData)); } }; diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 450f270db31..73df9bb8b9b 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -129,7 +129,7 @@ function wrapInImageBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); if (parent.window.frames[window.name]) { @@ -157,7 +157,7 @@ function wrapBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js index f9fc57a4834..467b628c944 100644 --- a/modules/welectBidAdapter.js +++ b/modules/welectBidAdapter.js @@ -38,7 +38,7 @@ export const spec = { let domain = bidRequest.params.domain || DEFAULT_DOMAIN; - let url = `https://${domain}/api/v2/preflight/by_alias/${bidRequest.params.placementId}`; + let url = `https://${domain}/api/v2/preflight/${bidRequest.params.placementId}`; let gdprConsent = null; diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index c1151617ac4..3cc0ab1194c 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,8 +1,8 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; import includes from 'core-js-pure/features/array/includes'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; @@ -10,10 +10,16 @@ const TIME_TO_LIVE = 300; const NET_REVENUE = true; const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; +const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; -const localWindow = utils.getWindowTop(); +const LOCAL_WINDOW = utils.getWindowTop(); +const DEFAULT_PLAYBACK_METHOD = 2; +const DEFAULT_START_DELAY = 0; +const VAST_TIMEOUT = 15000; +const MAX_BANNER_REQUEST_URL_LENGTH = 8000; +const BANNER_REQUEST_PROPERTIES_TO_REDUCE = ['description', 'title', 'pr', 'page_url']; export const spec = { code: BIDDER_CODE, @@ -47,13 +53,13 @@ export const spec = { p: [], page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), - pr: bidderRequest.refererInfo.referer, - scrd: localWindow.devicePixelRatio || 0, + pr: (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || '', + scrd: LOCAL_WINDOW.devicePixelRatio || 0, dnt: getDNT(), description: getPageDescription(), - title: localWindow.document.title || '', - w: localWindow.innerWidth, - h: localWindow.innerHeight, + title: LOCAL_WINDOW.document.title || '', + w: LOCAL_WINDOW.innerWidth, + h: LOCAL_WINDOW.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', @@ -88,6 +94,20 @@ export const spec = { } }); serverRequest.p = '[' + serverRequest.p.toString() + ']'; + + // check if url exceeded max length + const url = `${BANNER_SERVER_ENDPOINT}?${utils.parseQueryStringParameters(serverRequest)}`; + let extraCharacters = url.length - MAX_BANNER_REQUEST_URL_LENGTH; + if (extraCharacters > 0) { + for (let i = 0; i < BANNER_REQUEST_PROPERTIES_TO_REDUCE.length; i++) { + extraCharacters = shortcutProperty(extraCharacters, serverRequest, BANNER_REQUEST_PROPERTIES_TO_REDUCE[i]); + + if (extraCharacters <= 0) { + break; + } + } + } + serverRequests.push({ method: 'GET', url: BANNER_SERVER_ENDPOINT, @@ -167,8 +187,9 @@ function addPlacement(request) { if (request.params.placementId) { placementInfo.ym_placement_id = request.params.placementId; } - if (request.params.bidFloor) { - placementInfo.bidFloor = request.params.bidFloor; + const bidfloor = getBidFloor(request, BANNER); + if (bidfloor) { + placementInfo.bidFloor = bidfloor; } } return JSON.stringify(placementInfo); @@ -189,6 +210,10 @@ function createNewBannerBid(response) { netRevenue: NET_REVENUE, ttl: TIME_TO_LIVE, ad: response.ad, + meta: { + advertiserDomains: response.adomain || [], + mediaType: BANNER, + }, }; } @@ -198,8 +223,9 @@ function createNewBannerBid(response) { * @param bidRequest server request */ function createNewVideoBid(response, bidRequest) { - const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); - return { + const imp = (utils.deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid); + + let result = { requestId: imp.id, cpm: response.price, width: imp.video.w, @@ -209,8 +235,41 @@ function createNewVideoBid(response, bidRequest) { netRevenue: NET_REVENUE, mediaType: VIDEO, ttl: TIME_TO_LIVE, - vastXml: response.adm + vastXml: response.adm, + meta: { + advertiserDomains: response.adomain || [], + mediaType: VIDEO, + }, }; + + if (imp.video.placement && imp.video.placement !== 1) { + const renderer = Renderer.install({ + url: OUTSTREAM_VIDEO_PLAYER_URL, + config: { + width: result.width, + height: result.height, + vastTimeout: VAST_TIMEOUT, + maxAllowedVastTagRedirects: 5, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true + }, + id: imp.tagid, + loaded: false, + }); + + renderer.setRender(function (bid) { + bid.renderer.push(() => { + const { id, config } = bid.renderer; + window.YMoutstreamPlayer(bid, id, config); + }); + }); + + result.renderer = renderer; + } + + return result; } /** @@ -230,7 +289,7 @@ function getPageDescription() { if (document.querySelector('meta[name="description"]')) { return document .querySelector('meta[name="description"]') - .getAttribute('content'); // Value of the description metadata from the publisher's page. + .getAttribute('content') || ''; // Value of the description metadata from the publisher's page. } else { return ''; } @@ -280,7 +339,7 @@ function openRtbImpression(bidRequest) { const imp = { id: bidRequest.bidId, tagid: bidRequest.adUnitCode, - bidfloor: bidRequest.params.bidfloor || 0, + bidfloor: getBidFloor(bidRequest, VIDEO), ext: { placement_id: bidRequest.params.placementId }, @@ -297,13 +356,27 @@ function openRtbImpression(bidRequest) { .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = videoParams[param]); - if (videoParams.skippable) { - imp.video.skip = 1; + if (videoParams.skippable) imp.video.skip = 1; + if (videoParams.placement !== 1) { + imp.video = { + ...imp.video, + startdelay: DEFAULT_START_DELAY, + playbackmethod: [ DEFAULT_PLAYBACK_METHOD ] + } } - return imp; } +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ currency: CURRENCY, mediaType, size: '*' }); + } + + return floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.bidFloor || 0; +} + /** * @param {BidRequest} bidRequest bidder request object. * @return [number, number] || null Player's width and height, or undefined otherwise. @@ -457,3 +530,25 @@ function validateVideoParams(bid) { return false; } } + +/** + * Shortcut object property and check if required characters count was deleted + * + * @param {number} extraCharacters, count of characters to remove + * @param {object} target, object on which string property length should be reduced + * @param {string} propertyName, name of property to reduce + * @return {number} 0 if required characters count was removed otherwise count of how many left + */ +function shortcutProperty(extraCharacters, target, propertyName) { + if (target[propertyName].length > extraCharacters) { + target[propertyName] = target[propertyName].substring(0, target[propertyName].length - extraCharacters); + + return 0 + } + + const charactersLeft = extraCharacters - target[propertyName].length; + + target[propertyName] = ''; + + return charactersLeft; +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 1b8b7b1b741..54be295a1a1 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -69,3 +69,30 @@ var adUnits = [{ // Video adUnit }] }]; ``` + +Sample out-stream video ad unit config: +```javascript +var videoAdUnit = [{ + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'outstream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 3, // required, integer ( 3,4,5 ) + maxduration: 30, // required, integer + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [1,2] // required, array of integers + } + } + }] +}]; +``` diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js index d0ac64e48e4..b2c00b69c24 100644 --- a/modules/zemantaBidAdapter.js +++ b/modules/zemantaBidAdapter.js @@ -10,6 +10,7 @@ import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'zemanta'; +const GVLID = 164; const CURRENCY = 'USD'; const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; const NATIVE_PARAMS = { @@ -23,7 +24,10 @@ const NATIVE_PARAMS = { export const spec = { code: BIDDER_CODE, - aliases: ['outbrain'], + gvlid: GVLID, + aliases: [ + { code: 'outbrain', gvlid: GVLID } + ], supportedMediaTypes: [ NATIVE, BANNER ], isBidRequestValid: (bid) => { return ( @@ -37,6 +41,8 @@ export const spec = { const ua = navigator.userAgent; const test = setOnAny(validBidRequests, 'params.test'); const publisher = setOnAny(validBidRequests, 'params.publisher'); + const bcat = setOnAny(validBidRequests, 'params.bcat'); + const badv = setOnAny(validBidRequests, 'params.badv'); const cur = CURRENCY; const endpointUrl = config.getConfig('zemanta.bidderUrl') || config.getConfig('outbrain.bidderUrl'); const timeout = bidderRequest.timeout; @@ -73,7 +79,9 @@ export const spec = { source: { fd: 1 }, cur: [cur], tmax: timeout, - imp: imps + imp: imps, + bcat: bcat, + badv: badv, }; if (test) { @@ -131,14 +139,26 @@ export const spec = { bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; } + bidObject.meta = {}; + if (bidResponse.adomain && bidResponse.adomain.length > 0) { + bidObject.meta.advertiserDomains = bidResponse.adomain; + } return bidObject; } }).filter(Boolean); }, - getUserSyncs: (syncOptions) => { + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { const syncs = []; - const syncUrl = config.getConfig('zemanta.usersyncUrl') || config.getConfig('outbrain.usersyncUrl'); + let syncUrl = config.getConfig('zemanta.usersyncUrl') || config.getConfig('outbrain.usersyncUrl'); if (syncOptions.pixelEnabled && syncUrl) { + if (gdprConsent) { + syncUrl += '&gdpr=' + (gdprConsent.gdprApplies & 1); + syncUrl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + syncUrl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + syncs.push({ type: 'image', url: syncUrl @@ -147,7 +167,11 @@ export const spec = { return syncs; }, onBidWon: (bid) => { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server + // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute + if (bid.nurl) { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } } }; diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md index d991b67d429..fa933ecd922 100644 --- a/modules/zemantaBidAdapter.md +++ b/modules/zemantaBidAdapter.md @@ -65,7 +65,9 @@ pbjs.setConfig({ name: 'Publishers Name', domain: 'publisher.com' }, - tagid: 'tag-id' + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] } }] ]; @@ -94,7 +96,9 @@ pbjs.setConfig({ name: 'Publishers Name', domain: 'publisher.com' }, - tagid: 'tag-id' + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] }, }] ]; diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index 09d631b3d18..b168bf581d0 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -71,12 +71,22 @@ export const spec = { }; let payload = { id: bidderRequest.auctionId, - cur: [DEFAULT_CUR], imp: [impData], site: params.site ? params.site : {}, + app: params.app ? params.app : {}, device: params.device ? params.device : {}, user: params.user ? params.user : {}, - app: params.app ? params.app : {}, + at: params.at, + tmax: params.tmax, + wseat: params.wseat, + bseat: params.bseat, + allimps: params.allimps, + cur: [DEFAULT_CUR], + wlang: params.wlang, + bcat: params.bcat, + badv: params.badv, + bapp: params.bapp, + source: params.source ? params.source : {}, ext: { definerId: params.definerId } diff --git a/modules/zetaSspBidAdapter.js b/modules/zetaSspBidAdapter.js new file mode 100644 index 00000000000..8f4d8995800 --- /dev/null +++ b/modules/zetaSspBidAdapter.js @@ -0,0 +1,159 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'zeta_global_ssp'; +const ENDPOINT_URL = 'https:/ssp.disqus.com/bid'; +const USER_SYNC_URL = 'https:/ssp.disqus.com/match'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + // check for all required bid fields + if (!(bid && + bid.bidId && + bid.params)) { + utils.logWarn('Invalid bid request - missing required bid data'); + return false; + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const secure = 1; // treat all requests as secure + const request = validBidRequests[0]; + const params = request.params; + let impData = { + id: request.bidId, + secure: secure, + banner: buildBanner(request) + }; + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + let payload = { + id: bidderRequest.auctionId, + cur: [DEFAULT_CUR], + imp: [impData], + site: params.site ? params.site : {}, + device: fpd.device ? fpd.device : {}, + user: params.user ? params.user : {}, + app: params.app ? params.app : {}, + ext: { + tags: params.tags ? params.tags : {}, + sid: params.sid ? params.sid : {} + } + }; + + payload.device.ua = navigator.userAgent; + payload.site.page = bidderRequest.refererInfo.referer; + payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs = { + ext: { + gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user = { + ext: { + consent: request.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let bidResponse = []; + if (Object.keys(serverResponse.body).length !== 0) { + let zetaResponse = serverResponse.body; + let zetaBid = zetaResponse.seatbid[0].bid[0]; + let bid = { + requestId: zetaBid.impid, + cpm: zetaBid.price, + currency: zetaResponse.cur, + width: zetaBid.w, + height: zetaBid.h, + ad: zetaBid.adm, + ttl: TTL, + creativeId: zetaBid.crid, + netRevenue: NET_REV, + }; + if (zetaBid.adomain && zetaBid.adomain.length) { + bid.meta = {}; + bid.meta.advertiserDomains = zetaBid.adomain; + } + bidResponse.push(bid); + } + return bidResponse; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param gdprConsent The GDPR consent parameters + * @param uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + }); + } + return syncs; + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && + request.mediaTypes.banner && + request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], + h: sizes[0][1] + }; +} + +registerBidder(spec); diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md new file mode 100644 index 00000000000..d2950bce6b9 --- /dev/null +++ b/modules/zetaSspBidAdapter.md @@ -0,0 +1,42 @@ +# Overview + +``` +Module Name: Zeta Ssp Bidder Adapter +Module Type: Bidder Adapter +Maintainer: miakovlev@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's SSP + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: 'zeta_global_ssp', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + tags: { + someTag: 123, + sid: 'publisherId' + }, + test: 1 + } + } + ] + } + ]; +``` diff --git a/package-lock.json b/package-lock.json index 330298cbaac..5131b5f4d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24613,4 +24613,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index bcde3c402d2..9a70fa20102 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wmg-imgn/prebid.js", - "version": "4.20.0", + "version": "4.37.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/Renderer.js b/src/Renderer.js index 7cedf278537..c997658b30b 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -39,17 +39,22 @@ export function Renderer(options) { // use a function, not an arrow, in order to be able to pass "arguments" through this.render = function () { + const renderArgs = arguments + const runRender = () => { + if (this._render) { + this._render.apply(this, renderArgs) + } else { + utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + } + } + if (!isRendererPreferredFromAdUnit(adUnitCode)) { // we expect to load a renderer url once only so cache the request to load script + this.cmd.unshift(runRender) // should render run first ? loadExternalScript(url, moduleCode, this.callback); } else { utils.logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); - } - - if (this._render) { - this._render.apply(this, arguments) // _render is expected to use push as appropriate - } else { - utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + runRender() } }.bind(this) // bind the function to this object to avoid 'this' errors } diff --git a/src/adapterManager.js b/src/adapterManager.js index cb84607e130..f7f5d821932 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -68,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' diff --git a/src/adloader.js b/src/adloader.js index 1c18ce82b16..5460cc79410 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import * as utils from './utils.js'; const _requestCache = {}; // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + 'adloox', 'criteo', 'outstream', 'adagio', diff --git a/src/auction.js b/src/auction.js index ec9ff7d8209..cb7e0f60352 100644 --- a/src/auction.js +++ b/src/auction.js @@ -452,7 +452,7 @@ export function addBidToAuction(auctionInstance, bidResponse) { function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded) { let addBid = true; - const bidderRequest = getBidRequest(bidResponse.requestId, [bidRequests]); + const bidderRequest = getBidRequest(bidResponse.originalRequestId || bidResponse.requestId, [bidRequests]); const videoMediaType = bidderRequest && deepAccess(bidderRequest, 'mediaTypes.video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); @@ -519,7 +519,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); // a publisher-defined renderer can be used to render bids - const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode); + const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId); const adUnitRenderer = bidReq && bidReq.renderer; // a publisher can also define a renderer for a mediaType @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } diff --git a/src/auctionManager.js b/src/auctionManager.js index 3d4bd0afe99..1aca1277aa8 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -9,6 +9,7 @@ * * @property {function(): Array} getBidsRequested - returns consolidated bid requests * @property {function(): Array} getBidsReceived - returns consolidated bid received + * @property {function(): Array} getAllBidsForAdUnitCode - returns consolidated bid received for a given adUnit * @property {function(): Array} getAdUnits - returns consolidated adUnits * @property {function(): Array} getAdUnitCodes - returns consolidated adUnitCodes * @property {function(): Object} createAuction - creates auction instance and stores it for future reference @@ -66,6 +67,13 @@ export function newAuctionManager() { .filter(bid => bid); }; + auctionManager.getAllBidsForAdUnitCode = function(adUnitCode) { + return _auctions.map((auction) => { + return auction.getBidsReceived(); + }).reduce(flatten, []) + .filter(bid => bid && bid.adUnitCode === adUnitCode) + }; + auctionManager.getAdUnits = function() { return _auctions.map(auction => auction.getAdUnits()) .reduce(flatten, []); diff --git a/src/config.js b/src/config.js index daaf739bbbd..b10989e3a53 100644 --- a/src/config.js +++ b/src/config.js @@ -29,6 +29,7 @@ const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; const DEFAULT_DEVICE_ACCESS = true; +const DEFAULT_MAX_NESTED_IFRAMES = 10; const DEFAULT_TIMEOUTBUFFER = 400; @@ -200,6 +201,15 @@ export function newConfig() { this._disableAjaxTimeout = val; }, + // default max nested iframes for referer detection + _maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, + get maxNestedIframes() { + return this._maxNestedIframes; + }, + set maxNestedIframes(val) { + this._maxNestedIframes = val; + }, + _auctionOptions: {}, get auctionOptions() { return this._auctionOptions; @@ -324,6 +334,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + utils.mergeDeep(result, obj[type][key]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + let prop = (type === 'context') ? 'site' : type; + duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { + if (key === 'data') { + utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { + Object.keys(opt[type]).forEach((key) => { + if (key === 'data') { + utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + } else { + if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { + Object.keys(opt[type][key]).forEach(data => { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -338,13 +461,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -437,11 +561,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -499,7 +625,10 @@ export function newConfig() { runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/prebid.js b/src/prebid.js index 3452107effd..2211e8ec2b5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,12 +1,12 @@ /** @module pbjs */ import { getGlobal } from './prebidGlobal.js'; -import { adUnitsFilter, flatten, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; +import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; -import { targeting } from './targeting.js'; +import { filters, targeting } from './targeting.js'; import { hook } from './hook.js'; import { sessionLoader } from './debugging.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -43,6 +43,9 @@ $$PREBID_GLOBAL$$.libLoaded = true; $$PREBID_GLOBAL$$.version = 'v$prebid.version$'; utils.logInfo('Prebid.js v$prebid.version$ loaded'); +// modules list generated from build +$$PREBID_GLOBAL$$.installedModules = ['v$prebid.modulesList$']; + // create adUnit array $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; @@ -206,6 +209,24 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { } }; +/** + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode + * @returns {Object} returnObj return bid + */ +$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { + if (adunitCode) { + const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) + .filter(filters.isUnusedBid) + .filter(filters.isBidNotExpired) + + return bid.length ? bid.reduce(getHighestCpm) : {} + } else { + utils.logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); + } +}; + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for @@ -484,7 +505,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); - adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; + adUnits = (adUnits && config.convertAdUnitFpd(utils.isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); @@ -595,11 +616,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; diff --git a/src/refererDetection.js b/src/refererDetection.js index da68313736b..56d1fa43f7b 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -3,11 +3,12 @@ * The information that it tries to collect includes: * The detected top url in the nav bar, * Whether it was able to reach the top most window (if for example it was embedded in several iframes), - * The number of iframes it was embedded in if applicable, + * The number of iframes it was embedded in if applicable (by default max ten iframes), * A list of the domains of each embedded window if applicable. * Canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage */ +import { config } from './config.js'; import { logWarn } from './utils.js'; /** @@ -72,6 +73,7 @@ export function detectReferer(win) { function refererInfo() { const stack = []; const ancestors = getAncestorOrigins(win); + const maxNestedIframes = config.getConfig('maxNestedIframes'); let currentWindow; let bestReferrer; let bestCanonicalUrl; @@ -161,7 +163,7 @@ export function detectReferer(win) { stack.push(foundReferrer); level++; - } while (currentWindow !== win.top); + } while (currentWindow !== win.top && level < maxNestedIframes); stack.reverse(); diff --git a/src/storageManager.js b/src/storageManager.js index 60e5a7706d0..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } diff --git a/src/targeting.js b/src/targeting.js index cb53ea0fed3..365453e1e8f 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -4,6 +4,7 @@ import { NATIVE_TARGETING_KEYS } from './native.js'; import { auctionManager } from './auctionManager.js'; import { sizeSupported } from './sizeMapping.js'; import { ADPOD } from './mediaTypes.js'; +import { hook } from './hook.js'; import includes from 'core-js-pure/features/array/includes.js'; import find from 'core-js-pure/features/array/find.js'; @@ -33,26 +34,31 @@ export let filters = { // If two bids are found for same adUnitCode, we will use the highest one to take part in auction // This can happen in case of concurrent auctions // If adUnitBidLimit is set above 0 return top N number of bids -export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, adUnitBidLimit = 0) { - const bids = []; - const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); - // bucket by adUnitcode - let buckets = groupBy(bidsReceived, 'adUnitCode'); - // filter top bid for each bucket by bidder - Object.keys(buckets).forEach(bucketKey => { - let bucketBids = []; - let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); - // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit > 0) { - bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); - bids.push(...bucketBids.slice(0, adUnitBidLimit)); - } else { - bids.push(...bucketBids); - } - }); - return bids; -} +export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + if (!hasModified) { + const bids = []; + const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); + // bucket by adUnitcode + let buckets = groupBy(bidsReceived, 'adUnitCode'); + // filter top bid for each bucket by bidder + Object.keys(buckets).forEach(bucketKey => { + let bucketBids = []; + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); + Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); + // if adUnitBidLimit is set, pass top N number bids + if (adUnitBidLimit > 0) { + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); + bids.push(...bucketBids.slice(0, adUnitBidLimit)); + } else { + bids.push(...bucketBids); + } + }); + + return bids; + } + + return bidsReceived; +}) /** * A descending sort function that will sort the list of objects based on the following two dimensions: diff --git a/src/userSync.js b/src/userSync.js index fceeb1d722d..f653880fa29 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -225,7 +225,7 @@ export function newUserSync(userSyncDependencies) { } return checkForFiltering[filterType](biddersToFilter, bidder); } - return false; + return !permittedPixels[type]; } /** diff --git a/src/utils.js b/src/utils.js index bd3257ab7e7..7d273807ce7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -264,6 +264,7 @@ export function logWarn() { if (debugTurnedOn() && consoleWarnExists) { console.warn.apply(console, decorateLog(arguments, 'WARNING:')); } + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments}); } export function logError() { diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index b52ca5e9280..6e7f5756100 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -4,7 +4,7 @@ import * as adloader from 'src/adloader.js'; // this export is for adloader's tests against actual implementation export let loadExternalScript = adloader.loadExternalScript; -let stub = createStub(); +export let loadExternalScriptStub = createStub(); function createStub() { return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { @@ -18,6 +18,6 @@ function createStub() { } beforeEach(function() { - stub.restore(); - stub = createStub(); + loadExternalScriptStub.restore(); + loadExternalScriptStub = createStub(); }); diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 71fb9f87fa0..2b36848bd8f 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -49,8 +49,10 @@ FEATURE: Analytics Adapters API events.emit(eventType, args); adapter.enableAnalytics(); - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {wat: 'wot'}, eventType: 'bidResponse'}); + // As now AUCTION_DEBUG is triggered for WARNINGS too, the BID_RESPONSE goes last in the array + const index = server.requests.length - 1; + let result = JSON.parse(server.requests[index].requestBody); + expect(result).to.deep.equal({eventType: 'bidResponse', args: {wat: 'wot'}}); }); describe(`WHEN an event occurs after enable analytics\n`, function () { diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/adapters/adbutler_spec.js b/test/spec/adapters/adbutler_spec.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6d67565056f..cb6c7aa87a3 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -82,5 +82,9 @@ describe('Publisher API', function () { it('should have function $$PREBID_GLOBAL$$.getAllWinningBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getAllWinningBids); }); + + it('should have function $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode); + }); }); }); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 81ce966efb2..8161d9f1827 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,8 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +17,8 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -57,6 +61,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; @@ -195,6 +210,12 @@ describe('config API', function () { expect(getConfig('deviceAccess')).to.be.equal(true); }); + it('sets maxNestedIframes', function () { + expect(getConfig('maxNestedIframes')).to.be.equal(10); + setConfig({ maxNestedIframes: 2 }); + expect(getConfig('maxNestedIframes')).to.be.equal(2); + }); + it('should log error for invalid priceGranularity', function () { setConfig({ priceGranularity: '' }); const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index edc7b7a2767..c16e3fffd03 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -5,6 +5,14 @@ import { config } from 'src/config.js'; import { spec } from 'modules/33acrossBidAdapter.js'; +function validateBuiltServerRequest(builtReq, expectedReq) { + expect(builtReq.url).to.equal(expectedReq.url); + expect(builtReq.options).to.deep.equal(expectedReq.options); + expect(JSON.parse(builtReq.data)).to.deep.equal( + JSON.parse(expectedReq.data) + ) +} + describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; @@ -22,14 +30,9 @@ describe('33acrossBidAdapter:', function () { id: SITE_ID }, id: 'b1', - user: { - ext: { - } - }, regs: { ext: { - gdpr: 0, - us_privacy: null + gdpr: 0 } }, ext: { @@ -194,6 +197,18 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withUserIds = (eids) => { + Object.assign(ttxRequest, { + user: { + ext: { + eids + } + } + }); + + return this; + } + this.build = () => ttxRequest; } @@ -270,6 +285,12 @@ describe('33acrossBidAdapter:', function () { return this; } + this.withUserIds = (eids) => { + bidRequests[0].userIdAsEids = eids; + + return this; + }; + this.build = () => bidRequests; } @@ -568,7 +589,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 600, height: 400 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -585,7 +607,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -602,7 +625,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 800, height: 800 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -621,7 +645,8 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -643,7 +668,8 @@ describe('33acrossBidAdapter:', function () { sandbox.stub(utils, 'getWindowTop').returns({}); sandbox.stub(utils, 'getWindowSelf').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -664,7 +690,8 @@ describe('33acrossBidAdapter:', function () { win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -689,9 +716,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with gdpr consent data', function() { @@ -710,9 +737,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -731,9 +758,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default gdpr consent data', function() { @@ -751,9 +778,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -775,9 +802,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with us_privacy consent data', function() { @@ -796,9 +823,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -817,9 +844,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default us_privacy consent data', function() { @@ -837,9 +864,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -860,9 +887,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -880,9 +907,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -934,9 +961,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); }); @@ -952,9 +979,9 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -967,9 +994,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -984,9 +1011,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('sets bidfloors in ttxRequest if there is a floor', function() { @@ -1009,9 +1036,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1034,9 +1061,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds instream request with params passed', function() { @@ -1051,9 +1078,9 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); @@ -1075,9 +1102,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with video params passed', function() { @@ -1095,9 +1122,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1117,9 +1144,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds default inview request when product is set as such', function() { @@ -1138,9 +1165,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1162,9 +1189,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with banner and outstream video even when context is instream', function() { @@ -1186,9 +1213,9 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -1207,9 +1234,9 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); it('sets bidfloors in video if there is a floor', function() { @@ -1235,9 +1262,118 @@ describe('33acrossBidAdapter:', function () { .withFloors('video', [ 1.0 ]) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user ID data exists as userIdAsEids Array in bidRequest', function() { + it('passes userIds in eids field in ORTB request', function() { + const eids = [ + { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + ]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + it('does not validate eids ORTB', function() { + const eids = [1, 2, 3]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user IDs do not exist under the userIdAsEids field in bidRequest as a non-empty Array', function() { + it('does not pass user IDs in the bidRequest ORTB', function() { + const eidsScenarios = [ + 'foo', + [], + {foo: 1} + ]; - expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + eidsScenarios.forEach((eids) => { + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + bidRequests.userId = { + 'vendorx': { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); }); }); }); diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 3dccbb28426..cb05fa62ab6 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('a4gAdapterTests', function () { describe('bidRequestValidity', function () { @@ -139,15 +140,54 @@ describe('a4gAdapterTests', function () { const bidResponse = { body: [{ - 'id': 'div-gpt-ad-1460505748561-0', + 'id': '51ef8751f9aead', 'ad': 'test ad', 'width': 320, 'height': 250, - 'cpm': 5.2 + 'cpm': 5.2, + 'crid': '111' }], headers: {} }; + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + it('required keys', function () { const result = spec.interpretResponse(bidResponse, bidRequest); @@ -169,5 +209,11 @@ describe('a4gAdapterTests', function () { expect(requiredKeys.indexOf(key) !== -1).to.equal(true); }); }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.not.equal(result[0].adId); + }) }); }); diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js index 1f881897fd8..8b927ace84c 100644 --- a/test/spec/modules/adWMGBidAdapter_spec.js +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -168,8 +168,8 @@ describe('adWMGBidAdapter', function () { it('should have an url that match the default endpoint', function() { let requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[0].url).to.equal('https://rtb.adwmg.com/prebid'); - expect(requests[1].url).to.equal('https://rtb.adwmg.com/prebid'); + expect(requests[0].url).to.equal('https://hb.adwmg.com/hb'); + expect(requests[1].url).to.equal('https://hb.adwmg.com/hb'); }); it('should contain GDPR consent data if GDPR set', function() { @@ -258,7 +258,7 @@ describe('adWMGBidAdapter', function () { let syncs = spec.getUserSyncs(syncOptions); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + expect(syncs[0].url).includes('https://hb.adwmg.com/cphb.html?'); }); it('should register iframe sync when iframe and image are enabled', function () { @@ -269,7 +269,7 @@ describe('adWMGBidAdapter', function () { let syncs = spec.getUserSyncs(syncOptions); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + expect(syncs[0].url).includes('https://hb.adwmg.com/cphb.html?'); }); it('should send GDPR consent if enabled', function() { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 0a585caaa1a..3707c19e471 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -import { expect } from 'chai'; +import { expect, util } from 'chai'; import { _features, internal as adagio, @@ -13,7 +13,8 @@ import { } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -331,7 +332,8 @@ describe('Adagio bid adapter', () => { 'schain', 'prebidVersion', 'adapterVersion', - 'featuresVersion' + 'featuresVersion', + 'data' ]; it('groups requests by organizationId', function() { @@ -879,6 +881,174 @@ describe('Adagio bid adapter', () => { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); }); + + describe('Response with native add', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + required: 1, + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + bidRequestNative.mediaTypes = { + native: { + sendTargetingKeys: false, + + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} + } + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); + }); }); describe('getUserSyncs()', function() { diff --git a/test/spec/modules/addefendBidAdapter_spec.js b/test/spec/modules/addefendBidAdapter_spec.js new file mode 100644 index 00000000000..ac01750e98f --- /dev/null +++ b/test/spec/modules/addefendBidAdapter_spec.js @@ -0,0 +1,184 @@ +import {expect} from 'chai'; +import {spec} from 'modules/addefendBidAdapter.js'; + +describe('addefendBidAdapter', () => { + const defaultBidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + sizes: [[300, 250], [300, 600]], + params: { + pageId: 'stringid1', + placementId: 'stringid2' + } + }; + + const deepClone = function (val) { + return JSON.parse(JSON.stringify(val)); + }; + + const buildRequest = (buildRequest, bidderRequest) => { + if (!Array.isArray(buildRequest)) { + buildRequest = [buildRequest]; + } + + return spec.buildRequests(buildRequest, { + ...bidderRequest || {}, + refererInfo: { + referer: 'https://referer.example.com' + } + })[0]; + }; + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('pageId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.pageId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('placementId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.placementId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequest); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest); + + it('sends bid request to endpoint via https using post', () => { + expect(request.method).to.equal('POST'); + expect(request.url.indexOf('https://')).to.equal(0); + expect(request.url).to.equal(`${spec.hostname}/bid`); + }); + + it('contains prebid version parameter', () => { + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); + }); + + it('contains correct referer', () => { + expect(request.data.referer).to.equal('https://referer.example.com'); + }); + + it('contains auctionId', () => { + expect(request.data.auctionId).to.equal('ccc4c7cdfe11cfbd74065e6dd28413d8'); + }); + + it('contains pageId', () => { + expect(request.data.pageId).to.equal('stringid1'); + }); + + it('sends correct bid parameters', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(request.data.bids).to.deep.equal([ { + bidId: bidRequest.bidId, + placementId: bidRequest.params.placementId, + sizes: [ '300x250', '300x600' ], + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6' + } ]); + }); + + it('handles empty gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: {} + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles non-existent gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: null + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles properly filled gdpr string', () => { + const bidRequest = deepClone(defaultBidRequest); + const consentString = 'GDPR_CONSENT_STRING'; + const request = buildRequest(bidRequest, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + expect(request.data.gdpr_consent).to.be.equal(consentString); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 120, + 'netRevenue': true, + 'currency': 'EUR', + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const expectedResponse = [ + { + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 120, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true, + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.deep.equal(expectedResponse[0][key]); + }); + }); + + it('handles incomplete server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 60 + } + ]; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + const serverResponse = []; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/adformOpenRTBBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js similarity index 98% rename from test/spec/modules/adformOpenRTBBidAdapter_spec.js rename to test/spec/modules/adfBidAdapter_spec.js index 05788183e29..2c141d31bf8 100644 --- a/test/spec/modules/adformOpenRTBBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -1,14 +1,21 @@ // jshint esversion: 6, es3: false, node: true import {assert, expect} from 'chai'; -import {spec} from 'modules/adformOpenRTBBidAdapter.js'; +import {spec} from 'modules/adfBidAdapter.js'; import { NATIVE } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; -describe('AdformOpenRTB adapter', function () { +describe('Adf adapter', function () { let serverResponse, bidRequest, bidResponses; let bids = []; + describe('backwards-compatibility', function () { + it('should have adformOpenRTB alias defined', function () { + assert.equal(spec.aliases[0].code, 'adformOpenRTB'); + assert.equal(spec.aliases[0].gvlid, 50); + }); + }); + describe('isBidRequestValid', function () { let bid = { 'bidder': 'adformOpenRTB', @@ -567,7 +574,6 @@ describe('AdformOpenRTB adapter', function () { assert.deepEqual(bids[0].netRevenue, false); assert.deepEqual(bids[0].currency, serverResponse.body.cur); assert.deepEqual(bids[0].mediaType, 'native'); - assert.deepEqual(bids[0].bidderCode, 'adformOpenRTB'); }); it('should set correct native params', function () { const bid = [ diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 2ce40fd7ffb..526102c51fe 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -131,6 +131,12 @@ describe('AdheseAdapter', function () { expect(JSON.parse(req.data)).to.not.have.key('eids'); }); + it('should request vast content as url', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).vastContentAsUrl).to.equal(true); + }); + it('should include bids', function () { let bid = minimalBid(); let req = spec.buildRequests([ bid ], bidderRequest); @@ -180,7 +186,7 @@ describe('AdheseAdapter', function () { body: '
', tracker: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', impressionCounter: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', - extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}} + extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}, mediaType: 'banner'} } ] }; @@ -227,7 +233,7 @@ describe('AdheseAdapter', function () { width: '640', height: '350', body: '', - extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}} + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} } ] }; @@ -253,6 +259,43 @@ describe('AdheseAdapter', function () { expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('should get correct ssp cache video response', () => { + let sspCachedVideoResponse = { + body: [ + { + origin: 'RUBICON', + ext: 'js', + slotName: '_main_page_-leaderboard', + adType: 'leaderboard', + width: '640', + height: '350', + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + cpm: 2.1, + currency: 'USD', + creativeId: 'RUBICON', + dealId: '', + width: 640, + height: 350, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + adhese: { + origin: 'RUBICON', + originInstance: '', + originData: {} + } + }]; + expect(spec.interpretResponse(sspCachedVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should get correct Adhese banner response', () => { const adheseBannerResponse = { body: [ @@ -290,7 +333,8 @@ describe('AdheseAdapter', function () { amount: '5.96', currency: 'USD' } - } + }, + mediaType: 'banner' } } ] @@ -352,7 +396,10 @@ describe('AdheseAdapter', function () { impressionCounter: 'https://hosts-demo.adhese.com/track/742898', origin: 'JERLICIA', originData: {}, - auctionable: true + auctionable: true, + extension: { + mediaType: 'video' + } } ] }; @@ -390,6 +437,70 @@ describe('AdheseAdapter', function () { expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('should get correct Adhese cached video response', () => { + const adheseVideoResponse = { + body: [ + { + adType: 'preroll', + adFormat: '', + orderId: '22248', + adspaceId: '164196', + body: '', + height: '360', + width: '640', + extension: { + mediaType: 'video' + }, + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + libId: '89860', + id: '742470', + advertiserId: '2263', + ext: 'advar', + orderName: 'Smartphoto EOY-20181112', + creativeName: 'PREROLL', + slotName: '_main_page_-leaderboard', + slotID: '41711', + impressionCounter: 'https://hosts-demo.adhese.com/track/742898', + origin: 'JERLICIA', + originData: {}, + auctionable: true + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + adhese: { + origin: '', + originInstance: '', + originData: { + adFormat: '', + adId: '742470', + adType: 'preroll', + adspaceId: '164196', + libId: '89860', + orderProperty: undefined, + priority: undefined, + viewableImpressionCounter: undefined, + slotId: '41711', + slotName: '_main_page_-leaderboard', + advertiserId: '2263', + } + }, + cpm: 0, + currency: 'USD', + creativeId: '742470', + dealId: '22248', + width: 640, + height: 360, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + }]; + expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should return no bids for empty adserver response', () => { let adserverResponse = { body: [] }; expect(spec.interpretResponse(adserverResponse, bidRequest)).to.be.empty; diff --git a/test/spec/modules/adkernelAdnAnalytics_spec.js b/test/spec/modules/adkernelAdnAnalytics_spec.js index e7ef831080c..7af96c9321c 100644 --- a/test/spec/modules/adkernelAdnAnalytics_spec.js +++ b/test/spec/modules/adkernelAdnAnalytics_spec.js @@ -1,6 +1,6 @@ -import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter.js'; +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; import {expect} from 'chai'; -import adapterManager from 'src/adapterManager.js'; +import adapterManager from 'src/adapterManager'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); @@ -158,11 +158,16 @@ describe('', function () { sizes: [[300, 250]], bidId: '208750227436c1', bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + gdprConsent: { + consentString: 'CONSENT', + gdprApplies: true, + apiVersion: 2 + }, + uspConsent: '1---' }], auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 + timeout: 3000 }; const RESPONSE = { @@ -202,6 +207,10 @@ describe('', function () { events.getEvents.restore(); }); + after(function() { + sandbox.restore(); + }); + it('should be configurable', function () { adapterManager.registerAnalyticsAdapter({ code: 'adkernelAdn', @@ -221,7 +230,7 @@ describe('', function () { }); it('should handle auction init event', function () { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, bidderRequests: [REQUEST], timeout: 3000}); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); expect(ev[0]).to.be.eql({event: 'auctionInit'}); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 3d3e64aeec9..a3063df84fe 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelAdnBidAdapter.js'; +import {spec} from 'modules/adkernelAdnBidAdapter'; +import {config} from 'src/config'; describe('AdkernelAdn adapter', function () { const bid1_pub1 = { @@ -263,6 +264,14 @@ describe('AdkernelAdn adapter', function () { expect(bidRequests[0].user).to.have.property('gdpr', 0); expect(bidRequests[0].user).to.not.have.property('consent'); }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_pub1]); + config.resetConfig(); + expect(bidRequests[0]).to.have.property('user'); + expect(bidRequests[0].user).to.have.property('coppa', 1); + }); }); describe('video request building', () => { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 0d772423a22..aee24719c9c 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelBidAdapter.js'; -import * as utils from 'src/utils.js'; +import {spec} from 'modules/adkernelBidAdapter'; +import * as utils from 'src/utils'; import {NATIVE, BANNER, VIDEO} from 'src/mediaTypes'; -import {config} from 'src/config.js'; +import {config} from 'src/config'; describe('Adkernel adapter', function () { const bid1_zone1 = { @@ -241,6 +241,7 @@ describe('Adkernel adapter', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { @@ -343,6 +344,14 @@ describe('Adkernel adapter', function () { expect(bidRequest.user.ext).to.be.eql({'consent': 'test-consent-string'}); }); + it('should contain coppa if configured', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_zone1]); + let bidRequest = bidRequests[0]; + expect(bidRequest).to.have.property('regs'); + expect(bidRequest.regs).to.have.property('coppa', 1); + }); + it('should\'t contain consent string if gdpr isn\'t applied', function () { let [_, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', {gdprConsent: {gdprApplies: false}})); let bidRequest = bidRequests[0]; @@ -556,7 +565,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(12); + expect(spec.aliases).to.be.an('array').that.is.not.empty; }); }); diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9cc9c4ded4d --- /dev/null +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -0,0 +1,229 @@ +import adapterManager from 'src/adapterManager.js'; +import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/adlooxAnalyticsAdapter.js'; +import { AUCTION_COMPLETED } from 'src/auction.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import { EVENTS } from 'src/constants.json'; +import * as utils from 'src/utils.js'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +const analyticsAdapterName = 'adloox'; + +describe('Adloox Analytics Adapter', function () { + let sandbox; + + const esplode = 'esplode'; + + const bid = { + bidder: 'dummy', + adUnitCode: 'ad-slot-1', + mediaType: 'display' + }; + + const auctionDetails = { + auctionStatus: AUCTION_COMPLETED, + bidsReceived: [ + bid + ] + }; + + const analyticsOptions = { + js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', + client: 'adlooxtest', + clientid: 127, + platformid: 0, + tagid: 0, + params: { + dummy1: '%%client%%', + dummy2: '%%pbAdSlot%%', + dummy3: function(bid) { throw new Error(esplode) } + } + }; + + adapterManager.registerAnalyticsAdapter({ + code: analyticsAdapterName, + adapter: analyticsAdapter + }); + describe('enableAnalytics', function () { + describe('invalid options', function () { + it('should require options', function (done) { + adapterManager.enableAnalytics({ + provider: analyticsAdapterName + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + it('should reject non-string options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = function () { }; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + it('should reject non-function options.toselector', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.toselector = esplode; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + [ 'client', 'clientid', 'platformid', 'tagid' ].forEach(function (o) { + it('should require options.' + o, function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + delete analyticsOptionsLocal[o]; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + + it('should reject non-object options.params', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.params = esplode; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + }); + + describe('process', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: utils.deepClone(analyticsOptions) + }); + + expect(analyticsAdapter.context).is.not.null; + }); + + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + + sandbox.restore(); + sandbox = undefined; + }); + + describe('event', function () { + it('should preload the script on AUCTION_END only once', function (done) { + const insertElementStub = sandbox.stub(utils, 'insertElement'); + + const uri = utils.parseUrl(analyticsAdapter.url(analyticsOptions.js)); + const isLinkPreloadAsScript = function(arg) { + const href_uri = utils.parseUrl(arg.href); // IE11 requires normalisation (hostname always includes port) + return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; + }; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.callCount).to.equal(1); + + done(); + }); + + it('should inject verification JS on BID_WON', function (done) { + const url = analyticsAdapter.url(`${analyticsOptions.js}#`); + + const parent = document.createElement('div'); + + const slot = document.createElement('div'); + slot.id = bid.adUnitCode; + parent.appendChild(slot); + + const dummy = document.createElement('div'); + parent.appendChild(dummy); + + const querySelectorStub = sandbox.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); + + events.emit(EVENTS.BID_WON, bid); + + const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; + + expect(urlInserted.substr(0, url.length)).to.equal(url); + expect(moduleCode).to.equal(analyticsAdapterName); + expect(/[#&]creatype=2(&|$)/.test(urlInserted)).is.true; // prebid 'display' -> adloox '2' + expect(new RegExp('[#&]dummy3=' + encodeURIComponent('ERROR: ' + esplode) + '(&|$)').test(urlInserted)).is.true; + + done(); + }); + }); + + describe('command', function () { + it('should return config', function (done) { + const expected = utils.deepClone(analyticsOptions); + delete expected.js; + delete expected.toselector; + delete expected.params; + + analyticsCommand(COMMAND.CONFIG, null, function (response) { + expect(utils.deepEqual(response, expected)).is.true; + + done(); + }); + }); + + it('should return expanded URL', function (done) { + const data = { + url: 'https://example.com?', + args: [ + [ 'client', '%%client%%' ] + ], + bid: bid, + ids: true + }; + const expected = `${data.url}${data.args[0][0]}=${analyticsOptions.client}`; + + analyticsCommand(COMMAND.URL, data, function (response) { + expect(response.substr(0, expected.length)).is.equal(expected); + + done(); + }); + }); + + it('should inject tracking event', function (done) { + const data = { + eventType: EVENTS.BID_WON, + args: bid + }; + + analyticsCommand(COMMAND.TRACK, data, function (response) { + expect(response).is.undefined; + + done(); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js new file mode 100644 index 00000000000..18107b780db --- /dev/null +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -0,0 +1,81 @@ +import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; +import {getStorageManager} from '../../../src/storageManager.js'; + +export const storage = getStorageManager(); + +const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; +const getIdParams = {params: {pid: pid}}; +describe('admixerId tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if pid configParam was not passed when getId', function () { + admixerIdSubmodule.getId(); + expect(logErrorStub.callCount).to.be.equal(1); + + admixerIdSubmodule.getId({}); + expect(logErrorStub.callCount).to.be.equal(2); + + admixerIdSubmodule.getId({params: {}}); + expect(logErrorStub.callCount).to.be.equal(3); + + admixerIdSubmodule.getId({params: {pid: 123}}); + expect(logErrorStub.callCount).to.be.equal(4); + }); + + it('should NOT call the admixer id endpoint if gdpr applies but consent string is missing', function () { + let submoduleCallback = admixerIdSubmodule.getId(getIdParams, { gdprApplies: true }); + expect(submoduleCallback).to.be.undefined; + }); + + it('should call the admixer id endpoint', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call callback with user id', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({setData: {visitorid: '571058d70bce453b80e6d98b4f8a81e3'}}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('571058d70bce453b80e6d98b4f8a81e3'); + }); + + it('should continue to callback if ajax response 204', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 204 + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); +}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 54ff038c083..62073fc6aaa 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -4,25 +4,36 @@ import { spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; describe('adnuntiusBidAdapter', function () { - const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + const tzo = new Date().getTimezoneOffset(); + const ENDPOINT_URL = `https://delivery.adnuntius.com/i?tzo=${tzo}&format=json`; const adapter = newBidder(spec); + const bidRequests = [ { + bidId: '123', bidder: 'adnuntius', params: { auId: '8b6bc', network: 'adnuntius', }, - bidId: '123' + } - ]; + ] + + const singleBidRequest = { + bid: [ + { + bidId: '123', + } + ] + } const serverResponse = { body: { 'adUnits': [ { 'auId': '000000000008b6bc', - 'targetId': '', + 'targetId': '123', 'html': '

hi!

', 'matchedAdCount': 1, 'responseId': 'adn-rsp-1460129238', @@ -67,8 +78,15 @@ describe('adnuntiusBidAdapter', function () { 'lineItemId': 'scyjdyv3mzgdsnpf', 'layoutId': 'sw6gtws2rdj1kwby', 'layoutName': 'Responsive image' - } + }, + ] + }, + { + 'auId': '000000000008b6bc', + 'targetId': '456', + 'matchedAdCount': 0, + 'responseId': 'adn-rsp-1460129238', } ] } @@ -96,22 +114,24 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\"}]}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}]}'); }); }); describe('interpretResponse', function () { it('should return valid response when passed valid server response', function () { - const request = spec.buildRequests(bidRequests); - const interpretedResponse = spec.interpretResponse(serverResponse, request[0]); + const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); const ad = serverResponse.body.adUnits[0].ads[0] expect(interpretedResponse).to.have.lengthOf(1); expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.cpm.currency); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].html); expect(interpretedResponse[0].ttl).to.equal(360); }); diff --git a/test/spec/modules/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js index 737f1c284e1..92fd672df47 100644 --- a/test/spec/modules/adponeBidAdapter_spec.js +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -110,122 +110,109 @@ describe('adponeBidAdapter', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); -}); - -describe('interpretResponse', function () { - let serverResponse; - let bidRequest = { data: {id: '1234'} }; - - beforeEach(function () { - serverResponse = { - body: { - id: '2579e20c0bb89', - seatbid: [ - { - bid: [ - { - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - impid: '2579e20c0bb89_0', - price: 1, - adm: '', - adomain: [ - 'www.addomain.com' - ], - iurl: 'https://localhost11', - crid: 'creative111', - h: 250, - w: 300, - ext: { - dspid: 6 + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: {id: '1234'} }; + + beforeEach(function () { + serverResponse = { + body: { + id: '2579e20c0bb89', + seatbid: [ + { + bid: [ + { + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + impid: '2579e20c0bb89_0', + price: 1, + adm: '', + meta: { + adomain: [ + 'adpone.com' + ] + }, + iurl: 'https://localhost11', + crid: 'creative111', + h: 250, + w: 300, + ext: { + dspid: 6 + } } - } - ], - seat: 'adpone' - } - ], - cur: 'USD' - }, - }; - }); - - it('validate_response_params', function() { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); - expect(newResponse[0].requestId).to.be.equal('1234'); - expect(newResponse[0].cpm).to.be.equal(1); - expect(newResponse[0].width).to.be.equal(300); - expect(newResponse[0].height).to.be.equal(250); - expect(newResponse[0].currency).to.be.equal('USD'); - expect(newResponse[0].netRevenue).to.be.equal(true); - expect(newResponse[0].ttl).to.be.equal(300); - expect(newResponse[0].ad).to.be.equal(''); - }); + ], + seat: 'adpone' + } + ], + cur: 'USD' + }, + }; + }); - it('should correctly reorder the server response', function () { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse.length).to.be.equal(1); - expect(newResponse[0]).to.deep.equal({ - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - requestId: '1234', - cpm: 1, - width: 300, - height: 250, - creativeId: 'creative111', - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: '' + it('validate_response_params', function() { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); + expect(newResponse[0].requestId).to.be.equal('1234'); + expect(newResponse[0].cpm).to.be.equal(1); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].netRevenue).to.be.equal(true); + expect(newResponse[0].ttl).to.be.equal(300); + expect(newResponse[0].ad).to.be.equal(''); }); - }); - it('should not add responses if the cpm is 0 or null', function () { - serverResponse.body.seatbid[0].bid[0].price = 0; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]); + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + meta: { + advertiserDomains: [ + 'adpone.com' + ] + }, + requestId: '1234', + cpm: 1, + width: 300, + height: 250, + creativeId: 'creative111', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '' + }); + }); - serverResponse.body.seatbid[0].bid[0].price = null; - response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]) - }); - it('should add responses if the cpm is valid', function () { - serverResponse.body.seatbid[0].bid[0].price = 0.5; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.not.deep.equal([]); - }); -}); + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); -describe('getUserSyncs', function () { - it('Verifies that getUserSyncs is a function', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - }); - it('Verifies getUserSyncs returns expected result', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - expect(spec.getUserSyncs({iframeEnabled: true})).to.deep.equal({ - type: 'iframe', - url: 'https://eu-ads.adpone.com' + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]) + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); }); }); - it('Verifies that iframeEnabled: false returns an empty array', function () { - expect(spec.getUserSyncs({iframeEnabled: false})).to.deep.equal(EMPTY_ARRAY); - }); - it('Verifies that iframeEnabled: null returns an empty array', function () { - expect(spec.getUserSyncs(null)).to.deep.equal(EMPTY_ARRAY); - }); -}); -describe('test onBidWon function', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onBidWon({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); + describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); }); }); diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 11a6a14a353..596f3bade5d 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -224,12 +224,14 @@ describe('AdrelevantisAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') - .withArgs('fpd') + .withArgs('ortb2') .returns({ - context: { + site: { keywords: 'US Open', - data: { - category: 'sports/tennis' + ext: { + data: { + category: 'sports/tennis' + } } } }); diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js new file mode 100644 index 00000000000..c16bc5df5cb --- /dev/null +++ b/test/spec/modules/adriverBidAdapter_spec.js @@ -0,0 +1,323 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adriverBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +const ENDPOINT = 'https://pb.adriver.ru/cgi-bin/bid.cgi'; + +describe('adriverAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'adriver', + 'params': { + 'placementId': '55:test_placement', + 'siteid': 'testSiteID' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600], [300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let getAdUnitsStub; + const floor = 3; + + let bidRequests = [ + { + 'bidder': 'adriver', + 'params': { + 'placementId': '55:test_placement', + 'siteid': 'testSiteID', + 'dealid': 'dealidTest' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600], [300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + let floorTestData = { + 'currency': 'USD', + 'floor': floor + }; + bidRequests[0].getFloor = _ => { + return floorTestData; + }; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should exist currency', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.cur).to.exist; + }); + + it('should exist at', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.at).to.exist; + expect(payload.at).to.deep.equal(1); + }); + + it('should parse imp', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.imp[0]).to.exist; + expect(payload.imp[0].id).to.deep.equal('55:test_placement'); + + expect(payload.imp[0].ext).to.exist; + expect(payload.imp[0].ext.query).to.deep.equal('bn=15&custom=111=' + '30b31c1838de1e'); + + expect(payload.imp[0].banner).to.exist; + expect(payload.imp[0].banner.w).to.deep.equal(300); + expect(payload.imp[0].banner.h).to.deep.equal(250); + }); + + it('should parse pmp', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].pmp).to.exist; + + expect(payload.imp[0].pmp.deals).to.exist; + + expect(payload.imp[0].pmp.deals[0].bidfloor).to.exist; + expect(payload.imp[0].pmp.deals[0].bidfloor).to.deep.equal(3); + + expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.exist; + expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.deep.equal('RUB'); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + let response = { + 'id': '221594457-1615288400-1-46-', + 'bidid': 'D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA', + 'seatbid': [{ + 'bid': [{ + 'id': '1', + 'impid': '/19968336/header-bid-tag-0', + 'price': 4.29, + 'h': 250, + 'w': 300, + 'adid': '7121351', + 'adomain': ['http://ikea.com'], + 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA&bid=7121351&wprc=4.29&tuid=-1&custom=207=/19968336/header-bid-tag-0', + 'cid': '717570', + 'ext': '2c262a7058758d' + }] + }, { + 'bid': [{ + 'id': '1', + 'impid': '/19968336/header-bid-tag-0', + 'price': 17.67, + 'h': 600, + 'w': 300, + 'adid': '7121369', + 'adomain': ['http://ikea.com'], + 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=DdtToXX5cpTaMMxrJSEsOsUIXt3WmC3jOvuNI5DguDrY8edFG60Jg1M-iMkVNKQ4OiAdHSLPJLQQXMUXZfI9VbjMoGCb-zzOTPiMpshI&bid=7121369&wprc=17.67&tuid=-1&custom=207=/19968336/header-bid-tag-0', + 'cid': '717570', + 'ext': '2c262a7058758d' + }] + }], + 'cur': 'RUB' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '2c262a7058758d', + cpm: 4.29, + width: 300, + height: 250, + creativeId: '/19968336/header-bid-tag-0', + currency: 'RUB', + netRevenue: true, + ttl: 3000, + meta: { + advertiserDomains: ['http://ikea.com'] + }, + ad: '' + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }); + + describe('function _getFloor', function () { + let bidRequests = [ + { + bidder: 'adriver', + params: { + placementId: '55:test_placement', + siteid: 'testSiteID', + dealid: 'dealidTest', + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600], [300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + const floorTestData = { + 'currency': 'RUB', + 'floor': 1.50 + }; + + const bitRequestStandard = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestStandard[0].getFloor = () => { + return floorTestData; + }; + + it('valid BidRequests', function () { + const request = spec.buildRequests(bitRequestStandard); + const payload = JSON.parse(request.data); + + expect(typeof bitRequestStandard[0].getFloor).to.equal('function'); + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestEmptyCurrency = JSON.parse(JSON.stringify(bidRequests)); + + const floorTestDataEmptyCurrency = { + 'currency': 'RUB', + 'floor': 1.50 + }; + + bitRequestEmptyCurrency[0].getFloor = () => { + return floorTestDataEmptyCurrency; + }; + + it('empty currency', function () { + const request = spec.buildRequests(bitRequestEmptyCurrency); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestFloorNull = JSON.parse(JSON.stringify(bidRequests)); + + const floorTestDataFloorNull = { + 'currency': '', + 'floor': null + }; + + bitRequestFloorNull[0].getFloor = () => { + return floorTestDataFloorNull; + }; + + it('empty floor', function () { + const request = spec.buildRequests(bitRequestFloorNull); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(0); + }); + + const bitRequestGetFloorNotFunction = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestGetFloorNotFunction[0].getFloor = 0; + + it('bid.getFloor is not a function', function () { + const request = spec.buildRequests(bitRequestGetFloorNotFunction); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(0); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestGetFloorBySized = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestGetFloorBySized[0].getFloor = (requestParams = {currency: 'USD', mediaType: '*', size: '*'}) => { + if (requestParams.size.length === 2 && requestParams.size[0] === 300 && requestParams.size[1] === 250) { + return { + 'currency': 'RUB', + 'floor': 3.33 + } + } else { + return {} + } + }; + + it('bid.getFloor get size', function () { + const request = spec.buildRequests(bitRequestGetFloorBySized); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(3.33); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + }); +}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 67372216e96..e6916997133 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -16,6 +16,7 @@ const aliasEP = { onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/', navelix: 'https://ghb.hb.navelix.com/v2/auction/', + bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; @@ -88,6 +89,7 @@ const SERVER_DISPLAY_RESPONSE = { 'source': { 'aid': 12345, 'pubId': 54321 }, 'bids': [{ 'ad': '', + 'adUrl': 'adUrl', 'requestId': '2e41f65424c87c', 'creative_id': 342516, 'cmpId': 342516, @@ -171,6 +173,7 @@ const displayEqResponse = [{ netRevenue: true, currency: 'USD', ad: '', + adUrl: 'adUrl', height: 250, width: 300, ttl: 300, diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js index a7732d6aeb7..8e1c872d460 100644 --- a/test/spec/modules/adtrueBidAdapter_spec.js +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -8,6 +8,7 @@ describe('AdTrueBidAdapter', function () { const adapter = newBidder(spec) let bidRequests; let bidResponses; + let bidResponses2; beforeEach(function () { bidRequests = [ { @@ -48,34 +49,85 @@ describe('AdTrueBidAdapter', function () { } ]; bidResponses = { - 'body': { - 'id': '1610681506302', - 'seatbid': [ + body: { + id: '1610681506302', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '1', - 'impid': '201fb513ca24e9', - 'price': 2.880000114440918, - 'burl': 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', - 'adm': '', - 'adid': '1610681506302', - 'adomain': [ + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ 'adtrue.com' ], - 'cid': 'f6l0r6n', - 'crid': 'abc77au4', - 'attr': [], - 'w': 300, - 'h': 250 + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 } ], - 'seat': 'adtrue', - 'group': 0 + seat: 'adtrue', + group: 0 } ], - 'bidid': '1610681506302', - 'cur': 'USD' + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue' + } + ] + } + } + }; + bidResponses2 = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 2, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image' + }, + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus' + } + ] + } } }; }); @@ -349,8 +401,6 @@ describe('AdTrueBidAdapter', function () { }); }); describe('getUserSyncs', function () { - let USER_SYNC_URL_IFRAME = 'https://hb.adtrue.com/prebid/usersync?t=iframe&p=1212'; - let USER_SYNC_URL_IMAGE = 'https://hb.adtrue.com/prebid/usersync?t=img&p=1212'; let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -359,12 +409,23 @@ describe('AdTrueBidAdapter', function () { sandbox.restore(); }); it('execute as per config', function () { - expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: USER_SYNC_URL_IFRAME - }]); - expect(spec.getUserSyncs({iframeEnabled: false}, {}, undefined, undefined)).to.deep.equal([{ - type: 'image', url: USER_SYNC_URL_IMAGE + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses], undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); + // Multiple user sync output + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses2], undefined, undefined)).to.deep.equal([ + { + type: 'image', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }, + { + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + } + ]); + }); }); }); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index f9aaea308a7..551d50b60e7 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/adxcgBidAdapter.js'; import {deepClone, parseUrl} from 'src/utils.js'; +import * as utils from '../../../src/utils.js'; describe('AdxcgAdapter', function () { let bidBanner = { @@ -29,15 +30,15 @@ describe('AdxcgAdapter', function () { adzoneid: '20', video: { api: [2], - protocols: [1, 2], - mimes: ['video/mp4'], maxduration: 30 } }, mediaTypes: { video: { context: 'instream', - playerSize: [[640, 480]] + playerSize: [[640, 480]], + protocols: [1, 2], + mimes: ['video/mp4'], } }, adUnitCode: 'adunit-code', @@ -91,7 +92,7 @@ describe('AdxcgAdapter', function () { expect(spec.isBidRequestValid(bidBanner)).to.equal(true); }); - it('should return true when required params not found', function () { + it('should return false when required params not found', function () { expect(spec.isBidRequestValid({})).to.be.false; }); @@ -106,10 +107,6 @@ describe('AdxcgAdapter', function () { const simpleVideo = JSON.parse(JSON.stringify(bidVideo)); simpleVideo.params.adzoneid = 123; expect(spec.isBidRequestValid(simpleVideo)).to.be.false; - simpleVideo.params.mimes = [1, 2, 3]; - expect(spec.isBidRequestValid(simpleVideo)).to.be.false; - simpleVideo.params.mimes = 'bad type'; - expect(spec.isBidRequestValid(simpleVideo)).to.be.false; }); }); @@ -124,7 +121,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + expect(query.ver).to.equal('r20210330PB40'); expect(query.source).to.equal('pbjs10'); expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('1'); @@ -158,7 +155,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; // general part expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + expect(query.ver).to.equal('r20210330PB40'); expect(query.source).to.equal('pbjs10'); expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('20'); @@ -196,7 +193,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + expect(query.ver).to.equal('r20210330PB40'); expect(query.source).to.equal('pbjs10'); expect(query.pbjs).to.equal('$prebid.version$'); expect(query.adzoneid).to.equal('2379'); @@ -327,123 +324,164 @@ describe('AdxcgAdapter', function () { }; let BANNER_RESPONSE = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 300, - height: 250, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - ad: '' - } - ], - header: {someheader: 'fakedata'} + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + adm: '', + w: 300, + h: 250, + adomain: ['adomain.com'], + cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], + ext: { + crType: 'banner', + advertiser_id: '777', + advertiser_name: 'advertiser', + agency_name: 'agency' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; let BANNER_RESPONSE_WITHDEALID = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 300, - height: 250, - deal_id: '7722', - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - ad: '' - } - ], - header: {someheader: 'fakedata'} + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + dealid: '7722', + adm: '', + w: 300, + h: 250, + adomain: ['adomain.com'], + ext: { + crType: 'banner' + } + } + ] + }], + cur: 'USD' + } }; let VIDEO_RESPONSE = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 640, - height: 360, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - vastUrl: 'vastContentUrl' - } - ], - header: {someheader: 'fakedata'} + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + nurl: 'vastContentUrl', + adomain: ['adomain.com'], + w: 640, + h: 360, + ext: { + crType: 'video' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; - let NATIVE_RESPONSE = { - body: [ + let NATIVE_RESPONSEob = { + assets: [ { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 0, - height: 0, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - nativeResponse: { - assets: [ - { - id: 1, - required: 0, - title: { - text: 'titleContent' - } - }, - { - id: 2, - required: 0, - img: { - url: 'imageContent', - w: 600, - h: 600 - } - }, - { - id: 3, - required: 0, - data: { - label: 'DESC', - value: 'descriptionContent' - } - }, - { - id: 0, - required: 0, - data: { - label: 'SPONSORED', - value: 'sponsoredByContent' - } - }, - { - id: 5, - required: 0, - icon: { - url: 'iconContent', - w: 400, - h: 400 - } - } - ], - link: { - url: 'linkContent' - }, - imptrackers: ['impressionTracker1', 'impressionTracker2'] + id: 1, + required: 0, + title: { + text: 'titleContent' + } + }, + { + id: 2, + required: 0, + img: { + url: 'imageContent', + w: 600, + h: 600 + } + }, + { + id: 3, + required: 0, + data: { + label: 'DESC', + value: 'descriptionContent' + } + }, + { + id: 0, + required: 0, + data: { + label: 'SPONSORED', + value: 'sponsoredByContent' + } + }, + { + id: 5, + required: 0, + icon: { + url: 'iconContent', + w: 400, + h: 400 } } ], - header: {someheader: 'fakedata'} + link: { + url: 'linkContent' + }, + imptrackers: ['impressionTracker1', 'impressionTracker2'] + } + + let NATIVE_RESPONSE = { + body: { + id: 'auctionid', + bidid: '84ab500420319d', + seatbid: [{ + bid: [ + { + impid: '84ab500420319d', + price: 0.45, + crid: '42', + w: 0, + h: 0, + adm: JSON.stringify(NATIVE_RESPONSEob), + adomain: ['adomain.com'], + ext: { + crType: 'native' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; it('handles regular responses', function () { + expect(BANNER_RESPONSE).to.exist; + expect(BANNER_RESPONSE.body).to.exist; + expect(BANNER_RESPONSE.body.id).to.exist; + expect(BANNER_RESPONSE.body.seatbid[0]).to.exist; let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); expect(result).to.have.lengthOf(1); @@ -452,26 +490,30 @@ describe('AdxcgAdapter', function () { expect(result[0].width).to.equal(300); expect(result[0].height).to.equal(250); expect(result[0].creativeId).to.equal(42); - expect(result[0].cpm).to.equal(0.45); + expect(result[0].cpm).to.be.within(0.45, 0.46); expect(result[0].ad).to.equal(''); expect(result[0].currency).to.equal('USD'); expect(result[0].netRevenue).to.equal(true); expect(result[0].ttl).to.equal(300); expect(result[0].dealId).to.not.exist; + expect(result[0].meta.advertiserDomains[0]).to.equal('adomain.com'); + expect(result[0].meta.advertiserId).to.be.eql('777'); + expect(result[0].meta.advertiserName).to.be.eql('advertiser'); + expect(result[0].meta.agencyName).to.be.eql('agency'); + expect(result[0].meta.advertiserDomains).to.be.eql(['adomain.com']); + expect(result[0].meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); }); it('handles regular responses with dealid', function () { - let result = spec.interpretResponse( - BANNER_RESPONSE_WITHDEALID, - BIDDER_REQUEST - ); + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID); expect(result).to.have.lengthOf(1); expect(result[0].width).to.equal(300); expect(result[0].height).to.equal(250); expect(result[0].creativeId).to.equal(42); - expect(result[0].cpm).to.equal(0.45); + // expect(result[0].cpm).to.equal(0.45); + expect(result[0].cpm).to.be.within(0.45, 0.46); expect(result[0].ad).to.equal(''); expect(result[0].currency).to.equal('USD'); expect(result[0].netRevenue).to.equal(true); @@ -479,7 +521,7 @@ describe('AdxcgAdapter', function () { }); it('handles video responses', function () { - let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + let result = spec.interpretResponse(VIDEO_RESPONSE); expect(result).to.have.lengthOf(1); expect(result[0].width).to.equal(640); @@ -494,17 +536,19 @@ describe('AdxcgAdapter', function () { }); it('handles native responses', function () { - let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + let result = spec.interpretResponse(NATIVE_RESPONSE); expect(result[0].width).to.equal(0); expect(result[0].height).to.equal(0); - expect(result[0].mediaType).to.equal('native'); + expect(result[0].creativeId).to.equal(42); expect(result[0].cpm).to.equal(0.45); expect(result[0].currency).to.equal('USD'); expect(result[0].netRevenue).to.equal(true); expect(result[0].ttl).to.equal(300); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].native.clickUrl).to.equal('linkContent'); expect(result[0].native.impressionTrackers).to.deep.equal([ 'impressionTracker1', @@ -545,4 +589,65 @@ describe('AdxcgAdapter', function () { ); }); }); + + describe('on bidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + it('should replace burl for banner', function () { + const burl = 'burl=${' + 'AUCTION_PRICE}'; + const bid = { + 'bidderCode': 'adxcg', + 'width': 0, + 'height': 0, + 'statusMessage': 'Bid available', + 'adId': '3d0b6ff1dda89', + 'requestId': '2a423489e058a1', + 'mediaType': 'banner', + 'source': 'client', + 'ad': burl, + 'cpm': 0.66, + 'creativeId': '353538_591471', + 'currency': 'USD', + 'dealId': '', + 'netRevenue': true, + 'ttl': 300, + // 'nurl': nurl, + 'burl': burl, + 'isBurl': true, + 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', + 'responseTimestamp': 1556867386065, + 'requestTimestamp': 1556867385916, + 'bidder': 'adxcg', + 'adUnitCode': 'div-gpt-ad-1555415275793-0', + 'timeToRespond': 149, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.66', + 'pbAg': '0.65', + 'pbDg': '0.66', + 'pbCg': '', + 'size': '0x0', + 'adserverTargeting': { + 'hb_bidder': 'mgid', + 'hb_adid': '3d0b6ff1dda89', + 'hb_pb': '0.66', + 'hb_size': '0x0', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_banner_title': 'TITLE', + 'hb_banner_image': 'hb_banner_image:3d0b6ff1dda89', + 'hb_banner_icon': 'IconURL', + 'hb_banner_linkurl': 'hb_banner_linkurl:3d0b6ff1dda89' + }, + 'status': 'targetingSet', + 'params': [{'adzoneid': '20'}] + }; + spec.onBidWon(bid); + expect(bid.burl).to.deep.equal(burl); + }); + }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 4c108ed9f1d..c432ad1b32d 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -95,6 +95,29 @@ describe('Adyoulike Adapter', function () { } ]; + const bidRequestWithNativeImageType = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': + { + 'native': { + 'type': 'image', + 'additional': { + 'will': 'be', + 'sent': ['300x250'] + } + }, + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + const sentBidNative = { 'bid_id_0': { 'PlacementID': 'e622af275681965d3095808561a1e510', @@ -135,6 +158,37 @@ describe('Adyoulike Adapter', function () { } }; + const sentNativeImageType = { + 'additional': { + 'sent': [ + '300x250' + ], + 'will': 'be' + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': true + }, + 'cta': { + 'required': false + }, + 'icon': { + 'required': false + }, + 'image': { + 'required': true + }, + 'sponsoredBy': { + 'required': true + }, + 'title': { + 'required': true + }, + 'type': 'image' + }; + const bidRequestWithDCPlacement = [ { 'bidId': 'bid_id_0', @@ -341,6 +395,23 @@ describe('Adyoulike Adapter', function () { canonicalQuery.restore(); }); + it('Should expand short native image config type', function() { + const request = spec.buildRequests(bidRequestWithNativeImageType, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); + expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); + + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + expect(payload.Bids['bid_id_0'].Native).deep.equal(sentNativeImageType); + }); + it('should add gdpr/usp consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let uspConsentData = '1YCC'; @@ -525,7 +596,8 @@ describe('Adyoulike Adapter', function () { }, impressionTrackers: [ 'https://testPixelIMP.com/fake', - 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991' + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=INSERTION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991' ], javascriptTrackers: [ 'https://testJsIMP.com/fake.js' diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 766045b0f3e..f502d631c17 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -17,10 +17,12 @@ const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B= const sampleNurl = 'https://example.exchange/nurl'; const sampleFPD = { - context: { + site: { keywords: 'sample keywords', - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }, user: { @@ -31,7 +33,7 @@ const sampleFPD = { const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'fpd' ? sampleFPD : null + (arg) => arg === 'ortb2' ? sampleFPD : null ) withStub(); @@ -58,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -69,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -104,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -123,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -144,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -179,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -253,7 +295,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(sampleFPD) + expect(data.fpd2).to.deep.equal(sampleFPD) }); }); @@ -299,20 +341,24 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId]).to.deep.equal({ av: true, au: 'div-gpt-ad-example', + vd: {}, ms: [ [[320, 50]], [[300, 250]], [] ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, au: 'div-gpt-ad-example', + sc: {}, ms: [ [[320, 50]], [[300, 250]], @@ -320,7 +366,9 @@ describe('AmxBidAdapter', () => { ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -338,7 +386,15 @@ describe('AmxBidAdapter', () => { av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -383,9 +439,10 @@ describe('AmxBidAdapter', () => { ...baseBidResponse.meta, mediaType: BANNER, }, + mediaType: BANNER, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -396,20 +453,14 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + mediaType: VIDEO, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 8e74e19f420..78be0618594 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -160,6 +160,9 @@ describe('AolAdapter', function () { currency: 'USD', dealId: 'deal-id', netRevenue: true, + meta: { + advertiserDomains: [] + }, ttl: bidRequest.ttl }); }); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index da9a050a8de..3a71833bc3e 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai' -import { spec } from 'modules/apacdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -199,11 +200,34 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', + 'atype': 1, + 'ext': { + 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' + } + }] + }], }, { 'bidder': 'apacdex', @@ -300,10 +324,30 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + describe('debug test', function() { + beforeEach(function() { + config.setConfig({debug: true}); + }); + afterEach(function() { + config.setConfig({debug: false}); + }); + it('should return a properly formatted request with pbjs_debug is true', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1); + }); + }); }); describe('.interpretResponse', function () { @@ -601,4 +645,66 @@ describe('ApacdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + describe('getDomain', function () { + it('should return valid domain from publisherDomain config', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: pageUrl}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return valid domain from pageUrl argument', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal(pageUrl); + }); + }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 3a3f4effcb8..935bc23be03 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -796,7 +796,7 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); - it('should set withCredentials to false if purpose 1 consent is not given', function () { + it('should set simple domain variant if purpose 1 consent is not given', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { 'bidderCode': 'appnexus', @@ -819,7 +819,7 @@ describe('AppNexusAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.options).to.deep.equal({withCredentials: false}); + expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); }); it('should populate eids when supported userIds are available', function () { diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index fca1a464ff2..9d828bad4c3 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -111,6 +111,7 @@ describe('automatadBidAdapter', function () { it('should get the correct bid response', function () { let result = spec.interpretResponse(expectedResponse[0]) expect(result).to.be.an('array').that.is.not.empty + expect(result[0].meta.advertiserDomains[0]).to.equal('someAdDomain'); }) it('should interpret multiple bids in seatbid', function () { diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js index 632e080f83d..aac1cbe08ff 100644 --- a/test/spec/modules/axonixBidAdapter_spec.js +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -179,7 +179,8 @@ describe('AxonixBidAdapter', function () { expect(data.site).to.have.property('page', 'https://www.prebid.org'); expect(data).to.have.property('validBidRequest', BANNER_REQUEST); - expect(data).to.have.property('connectiontype').to.exist; + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; expect(data).to.have.property('devicetype', 2); expect(data).to.have.property('bidfloor', 0); expect(data).to.have.property('dnt', 0); @@ -237,7 +238,8 @@ describe('AxonixBidAdapter', function () { expect(data.site).to.have.property('page', 'https://www.prebid.org'); expect(data).to.have.property('validBidRequest', VIDEO_REQUEST); - expect(data).to.have.property('connectiontype').to.exist; + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; expect(data).to.have.property('devicetype', 2); expect(data).to.have.property('bidfloor', 0); expect(data).to.have.property('dnt', 0); diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 44a8d2cc733..1b1ccd9efd2 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -221,4 +221,50 @@ describe('betweenBidAdapterTests', function () { expect(req_data.sizes).to.deep.equal(['970x250', '240x400', '728x90']) }); + + it('check sharedId with id and third', function() { + const bidRequestData = [{ + bidId: 'bid123', + bidder: 'between', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + s: 1112, + }, + userId: { + sharedid: { + id: '01EXQE7JKNDRDDVATB0S2GX1NT', + third: '01EXQE7JKNDRDDVATB0S2GX1NT' + } + } + }]; + const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; + const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; + expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT'); + }); + it('check sharedId with only id', function() { + const bidRequestData = [{ + bidId: 'bid123', + bidder: 'between', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + s: 1112, + }, + userId: { + sharedid: { + id: '01EXQE7JKNDRDDVATB0S2GX1NT', + } + } + }]; + const shid = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid; + const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; + expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal(''); + }); }); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 39ad4ae39c9..e0698c9eda8 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -53,7 +54,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -172,6 +177,22 @@ const NATIVE_BID_RESPONSE = { }; describe('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function() { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); @@ -225,6 +246,12 @@ describe('BizzclickAdapter', function() { expect(request.method).to.equal('POST'); }); + it('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 56ea29d6d73..3aee6c69438 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -210,6 +210,25 @@ describe('BlueBillywigAdapter', () => { bid.params.video = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should fail if rendererSettings is specified but is not an object', () => { + const bid = deepClone(baseValidBid); + + bid.params.rendererSettings = null; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', () => { diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js index f6312253722..bc7f69f0baf 100644 --- a/test/spec/modules/brightMountainMediaBidAdapter_spec.js +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -4,7 +4,7 @@ import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; describe('brightMountainMediaBidAdapter_spec', function () { let bid = { bidId: '2dd581a2b6281d', - bidder: 'brightmountainmedia', + bidder: 'bmtm', bidderRequestId: '145e1d6a7837c9', params: { placement_id: '123qwerty' @@ -19,7 +19,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', }; let bidderRequest = { - bidderCode: 'brightmountainmedia', + bidderCode: 'bmtm', auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', bidderRequestId: 'ffffffffffffff', start: 1472239426002, @@ -55,7 +55,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://console.brightmountainmedia.com/hb/bid'); + expect(serverRequest.url).to.equal('https://one.elitebidder.com/api/hb'); }); it('Returns valid data if array of bids is valid', function () { diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index f14612629b1..ef86b391e39 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -337,7 +337,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - ad: '' + ad: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } }, { requestId: '2e56e1af51a5d8', @@ -348,7 +351,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - vastXml: '' + vastXml: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } } ]; expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); @@ -366,7 +372,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - ad: '' + ad: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } } ]; expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index d802cd288ef..1c24fb7694a 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -339,6 +339,7 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('creativeId', '1000'); expect(bid).to.have.property('width', 300); expect(bid).to.have.property('height', 250); + expect(bid.meta.advertiserDomains).to.deep.equal(['https://example.com']); expect(bid).to.have.property('ad', 'markup000'); expect(bid).to.have.property('ttl', 300); expect(bid).to.have.property('netRevenue', true); @@ -483,7 +484,7 @@ describe('Conversant adapter tests', function() { const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'liveramp.com', uids: [{id: '334455', atype: 1}]} + {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} ]); }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e91d3b10abb..cad1e3f8114 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -820,14 +820,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +846,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +858,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +867,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index 6d9b883e2bb..fcf7056fb3f 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -202,6 +202,7 @@ describe('Deepintent adapter', function () { expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); expect(bResponse[0].currency).to.equal('USD'); expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['deepintent.com']); expect(bResponse[0].ttl).to.equal(300); expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js new file mode 100644 index 00000000000..7ea5553393c --- /dev/null +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { config } from 'src/config.js'; + +const DI_COOKIE_NAME = '_dpes_id'; +const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; +const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; + +const cookieConfig = { + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 28 + } +}; + +const html5Config = { + name: 'deepintentId', + storage: { + type: 'html5', + name: '_dpes_id', + expires: 28 + } +} + +describe('Deepintent DPES System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('Deepintent Dpes Sytsem: test "getId" method', () => { + it('Wrong config should fail the tests', () => { + // no config + expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); + + expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + }); + + it('Get value stored in cookie for getId', () => { + getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); + expect(diId).to.deep.equal(DI_COOKIE_OBJECT); + }); + + it('provides the stored deepintentId if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); + expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + }); + }); + + describe('Deepintent Dpes System : test "decode" method', () => { + it('Get the correct decoded value for dpes id', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + }); + }); +}); diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js new file mode 100644 index 00000000000..efff2efa319 --- /dev/null +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -0,0 +1,97 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/docereeBidAdapter.js'; +import { config } from '../../../src/config.js'; + +describe('BidlabBidAdapter', function () { + config.setConfig({ + doceree: { + context: { + data: { + token: 'testing-token', // required + } + }, + user: { + data: { + gender: '', + email: '', + hashedEmail: '', + firstName: '', + lastName: '', + npi: '', + hashedNPI: '', + city: '', + zipCode: '', + specialization: '', + } + } + } + }); + let bid = { + bidId: 'testing', + bidder: 'doceree', + params: { + placementId: 'DOC_7jm9j5eqkl0xvc5w', + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if placementId is present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if placementId is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + serverRequest = serverRequest[0] + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + }); + it('Returns GET method', function () { + expect(serverRequest.method).to.equal('GET'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&'); + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: { + DIVID: 'DOC_7jm9j5eqkl0xvc5w', + creativeType: 'banner', + guid: 'G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q', + currency: 'USD', + cpmBid: 2, + height: '250', + width: '300', + ctaLink: 'https://doceree.com/', + sourceURL: '', + sourceHTML: '
test
', + advertiserDomain: 'doceree.com', + } + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', + 'netRevenue', 'currency', 'mediaType', 'creativeId', 'meta'); + expect(dataItem.requestId).to.equal('G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q'); + expect(dataItem.cpm).to.equal(2); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('
test
'); + expect(dataItem.ttl).to.equal(30); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.creativeId).to.equal('DOC_7jm9j5eqkl0xvc5w'); + expect(dataItem.meta.advertiserDomains).to.be.an('array').that.is.not.empty; + expect(dataItem.meta.advertiserDomains[0]).to.equal('doceree.com') + }); + }) +}); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index cf36c3f62c4..87752f7747a 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -283,4 +283,60 @@ describe('dspxAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); }); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 659e5257e90..1ccaab2b302 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -84,13 +84,18 @@ describe('eids array generation for known sub-modules', function() { it('merkleId', function() { const userId = { - merkleId: 'some-random-id-value' + merkleId: { + id: 'some-random-id-value', keyID: 1 + } }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'merkleinc.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 + }}] }); }); @@ -102,7 +107,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveramp.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -117,7 +122,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}], + uids: [{id: 'some-random-id-value', atype: 3}], ext: {segments: ['s1', 's2']} }); }); @@ -132,7 +137,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -144,7 +149,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -184,6 +189,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('deepintentId', function() { + const userId = { + deepintentId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' @@ -195,6 +212,18 @@ describe('eids array generation for known sub-modules', function() { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + + it('NextRollId', function() { + const userId = { + nextrollId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'nextroll.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); it('Sharedid', function() { const userId = { sharedid: { @@ -276,6 +305,20 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('uid2', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); it('pubProvidedId', function() { const userId = { pubProvidedId: [{ diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 066ab6b21f6..6b75af0d55d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -13,7 +13,7 @@ describe('FeedAdAdapter', function () { it('should only support video and banner ads', function () { expect(spec.supportedMediaTypes).to.be.a('array'); expect(spec.supportedMediaTypes).to.include(BANNER); - expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); it('should export the BidderSpec functions', function () { @@ -23,6 +23,9 @@ describe('FeedAdAdapter', function () { expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); }); + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(781); + }) }); describe('isBidRequestValid', function () { @@ -248,6 +251,40 @@ describe('FeedAdAdapter', function () { let result = spec.buildRequests([bid, bid, bid]); expect(result).to.be.empty; }); + it('should not include GDPR data if the bidder request has none available', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.gdprApplies).to.be.undefined; + expect(result.data.consentIabTcf).to.be.undefined; + }); + it('should include GDPR data if the bidder requests contains it', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let request = Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'the consent string', + gdprApplies: true + } + }); + let result = spec.buildRequests([bid], request); + expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); + expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); + }); }); describe('interpretResponse', function () { @@ -404,7 +441,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.0' + sdk_version: '1.0.2' }; subject(data); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js new file mode 100644 index 00000000000..40f8ad50d78 --- /dev/null +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/gnetBidAdapter.js'; +import { + newBidder +} from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; + +describe('gnetAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + bidder: 'gnet', + params: { + websiteId: '4', + externalId: '4d52cccf30309282256012cf30309282' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + bidder: 'gnet', + params: { + websiteId: '4', + externalId: '4d52cccf30309282256012cf30309282' + }, + adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', + sizes: [ + [300, 250], + ], + bidId: '2a19afd5173318', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5' + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://gnetproject.com/' + } + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].data).to.equal(JSON.stringify({ + 'referer': 'https://gnetproject.com/', + 'adUnitCode': '/150790500/4_ZONA_IAB_300x250_5', + 'bidId': '2a19afd5173318', + 'transactionId': '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + 'sizes': ['300x250'], + 'params': { + 'websiteId': '4', + 'externalId': '4d52cccf30309282256012cf30309282' + } + })); + }); + }); + + describe('interpretResponse', function () { + const bidderRequests = [{ + bidder: 'gnet', + params: { + clientId: '123456' + }, + adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', + sizes: [ + [300, 250], + ], + bidId: '2a19afd5173318', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5' + }]; + + it('should get correct banner bid response', function () { + const response = { + bids: [ + { + bidId: '2a19afd5173318', + cpm: 0.1, + currency: 'BRL', + width: 300, + height: 250, + ad: '

I am an ad

', + creativeId: '173560700', + } + ] + }; + + const expectedResponse = [ + { + requestId: '2a19afd5173318', + cpm: 0.1, + currency: 'BRL', + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 300, + creativeId: '173560700', + netRevenue: true + } + ]; + + const result = spec.interpretResponse({ + body: response + }, bidderRequests); + expect(result).to.have.lengthOf(1); + expect(result).to.deep.have.same.members(expectedResponse); + }); + + it('handles nobid responses', function () { + const response = ''; + + const result = spec.interpretResponse({ + body: response + }, bidderRequests); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/gothamadsBidAdapter_spec.js b/test/spec/modules/gothamadsBidAdapter_spec.js index c638e7a0e3e..f0a3ea253f3 100644 --- a/test/spec/modules/gothamadsBidAdapter_spec.js +++ b/test/spec/modules/gothamadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/gothamadsBidAdapter.js'; +import { config } from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -44,7 +45,10 @@ const BANNER_BID_REQUEST = { code: 'banner_example', mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } }, bidder: 'gothamads', @@ -53,7 +57,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -65,26 +73,27 @@ const bidRequest = { const VIDEO_BID_REQUEST = { code: 'video1', sizes: [640, 480], - mediaTypes: { video: { - minduration: 0, - maxduration: 999, - boxingallowed: 1, - skip: 0, - mimes: [ - 'application/javascript', - 'video/mp4' - ], - w: 1920, - h: 1080, - protocols: [ - 2 - ], - linearity: 1, - api: [ - 1, - 2 - ] - } + mediaTypes: { + video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } }, bidder: 'gothamads', @@ -148,20 +157,29 @@ const NATIVE_BID_RESPONSE = { impid: 'request_imp_id', price: 5, adomain: ['example.com'], - adm: { native: + adm: { + native: { + assets: [{ + id: 0, + title: 'dummyText' + }, + { + id: 3, + image: imgData + }, { - assets: [ - {id: 0, title: 'dummyText'}, - {id: 3, image: imgData}, - { - id: 5, - data: {value: 'organization.name'} - } - ], - link: {url: 'example.com'}, - imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], - jstracker: 'tracker1.com' + id: 5, + data: { + value: 'organization.name' + } } + ], + link: { + url: 'example.com' + }, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } }, crid: 'crid', ext: { @@ -171,8 +189,23 @@ const NATIVE_BID_RESPONSE = { }], }; -describe('GothamAdsAdapter', function() { - describe('isBidRequestValid', function() { +describe('GothamAdsAdapter', function () { + describe('with COPPA', function () { + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function () { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); }); @@ -221,6 +254,12 @@ describe('GothamAdsAdapter', function() { expect(request.data).to.exist; }); + it('check consent and ccpa string is set properly', function () { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }); + it('sends bid request to our endpoint via POST', function () { expect(request.method).to.equal('POST'); }); @@ -250,7 +289,7 @@ describe('GothamAdsAdapter', function() { }); describe('interpretResponse', function () { - it('Empty response must return empty array', function() { + it('Empty response must return empty array', function () { const emptyResponse = null; let response = spec.interpretResponse(emptyResponse); @@ -273,6 +312,7 @@ describe('GothamAdsAdapter', function() { creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, mediaType: 'banner', + meta: BANNER_BID_RESPONSE.seatbid[0].bid[0].adomain, ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm } @@ -281,13 +321,14 @@ describe('GothamAdsAdapter', function() { expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); expect(dataItem.ad).to.equal(expectedBidResponse.ad); expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); expect(dataItem.netRevenue).to.be.true; + expect(dataItem.meta).to.have.property('advertiserDomains', expectedBidResponse.meta); expect(dataItem.currency).to.equal(expectedBidResponse.currency); expect(dataItem.width).to.equal(expectedBidResponse.width); expect(dataItem.height).to.equal(expectedBidResponse.height); @@ -309,6 +350,7 @@ describe('GothamAdsAdapter', function() { creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, mediaType: 'video', + meta: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adomain, vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl } @@ -318,12 +360,13 @@ describe('GothamAdsAdapter', function() { expect(videoResponses).to.be.an('array').that.is.not.empty; let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml); expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.meta).to.have.property('advertiserDomains', expectedBidResponse.meta); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal(expectedBidResponse.currency); expect(dataItem.width).to.equal(expectedBidResponse.width); @@ -345,8 +388,11 @@ describe('GothamAdsAdapter', function() { netRevenue: true, creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, + meta: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adomain, mediaType: 'native', - native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} + native: { + clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url + } } let nativeResponses = spec.interpretResponse(nativeResponse); @@ -354,11 +400,12 @@ describe('GothamAdsAdapter', function() { expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl); expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.meta).to.have.property('advertiserDomains', expectedBidResponse.meta); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal(expectedBidResponse.currency); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 16b84467af2..c4a81c21d5c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -30,34 +30,34 @@ describe('GPT pre-auction module', () => { '
test2
'; it('should be unchanged if already defined on adUnit', () => { - const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); }); it('should use adUnit.code if matching id exists', () => { - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); }); it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { - const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); }); it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { - const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; adUnit.code = 'foo5'; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); }); it('should use the adUnit.code if all other sources failed', () => { - const adUnit = { code: 'foo4', fpd: { context: {} } }; + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); }); it('should use the customPbAdSlot function if one is given', () => { @@ -67,32 +67,32 @@ describe('GPT pre-auction module', () => { } }); - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); }); }); describe('appendGptSlots', () => { it('should not add adServer object to context if no slots defined', () => { - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should not add adServer object to context if no slot matches', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should add adServer object to context if matching slot is found', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); }); it('should use the customGptSlotMatching function if one is given', () => { @@ -104,10 +104,10 @@ describe('GPT pre-auction module', () => { }); window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); }); }); @@ -164,23 +164,23 @@ describe('GPT pre-auction module', () => { it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { const testAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890' } } + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } }, { code: 'slotCode3', }]; const expectedAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } }, { code: 'slotCode3', - fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index df119bb689d..f2e70614562 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -400,6 +400,16 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); @@ -418,6 +428,58 @@ describe('TheMediaGrid Adapter', function () { expect(payload.tmax).to.equal(3000); getConfigStub.restore(); }); + it('should contain regs.coppa if coppa is true in config', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'coppa' ? true : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('coppa', 1); + getConfigStub.restore(); + }); + it('should contain imp[].ext.data.adserver if available', function() { + const ortb2Imp = [{ + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/111111/slot' + }, + pbadslot: '/111111/slot' + } + } + }, { + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].ext).to.deep.equal({ + divid: bidRequests[0].adUnitCode, + data: ortb2Imp[0].ext.data, + gpid: ortb2Imp[0].ext.data.adserver.adslot + }); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + }); describe('floorModule', function () { const floorTestData = { 'currency': 'USD', diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..2aec9713000 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -300,7 +300,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -317,7 +316,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0d37c8b1d25..a7b18a16173 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -258,6 +258,10 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.fp).to.equal(bidfloor); }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) }); it('sends bid request to ENDPOINT via GET', function () { @@ -457,37 +461,40 @@ describe('gumgumAdapter', function () { }) describe('interpretResponse', function () { - let serverResponse = { - 'ad': { - 'id': 29593, - 'width': 300, - 'height': 250, - 'ipd': 2000, - 'markup': '

I am an ad

', - 'ii': true, - 'du': null, - 'price': 0, - 'zi': 0, - 'impurl': 'http://g2.gumgum.com/ad/view', - 'clsurl': 'http://g2.gumgum.com/ad/close' + const metaData = { adomain: ['advertiser.com'], mediaType: BANNER } + const serverResponse = { + ad: { + id: 29593, + width: 300, + height: 250, + ipd: 2000, + markup: '

I am an ad

', + ii: true, + du: null, + price: 0, + zi: 0, + impurl: 'http://g2.gumgum.com/ad/view', + clsurl: 'http://g2.gumgum.com/ad/close' }, - 'pag': { - 't': 'ggumtest', - 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', - 'css': 'html { overflow-y: auto }', - 'js': 'console.log("environment", env);' + pag: { + t: 'ggumtest', + pvid: 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + css: 'html { overflow-y: auto }', + js: 'console.log("environment", env);' }, - 'jcsi': { t: 0, rq: 8 }, - 'thms': 10000 + jcsi: { t: 0, rq: 8 }, + thms: 10000, + meta: metaData } - let bidRequest = { + const bidRequest = { id: 12345, sizes: [[300, 250], [1, 1]], url: ENDPOINT, method: 'GET', pi: 3 } - let expectedResponse = { + const expectedMetaData = { advertiserDomains: ['advertiser.com'], mediaType: BANNER }; + const expectedResponse = { ad: '

I am an ad

', cpm: 0, creativeId: 29593, @@ -497,19 +504,42 @@ describe('gumgumAdapter', function () { requestId: 12345, width: '300', mediaType: BANNER, - ttl: 60 + ttl: 60, + meta: expectedMetaData }; it('should get correct bid response', function () { expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); }); + it('should set a default value for advertiserDomains if adomain is not found', function () { + const meta = { ...metaData }; + delete meta.adomain; + + const response = { ...serverResponse, meta }; + const expectedMeta = { ...expectedMetaData, advertiserDomains: [] }; + const expected = { ...expectedResponse, meta: expectedMeta }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + + it('should set a default value for meta.mediaType if mediaType is not found in the response', function () { + const meta = { ...metaData }; + delete meta.mediaType; + const response = { ...serverResponse, meta }; + const expected = { ...expectedResponse }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + it('should pass correct currency if found in bid response', function () { const cur = 'EURO'; - let response = Object.assign({}, serverResponse); - let expected = Object.assign({}, expectedResponse); + const response = { ...serverResponse }; response.ad.cur = cur; + + const expected = { ...expectedResponse }; expected.currency = cur; + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); }); @@ -534,6 +564,25 @@ describe('gumgumAdapter', function () { expect(result.length).to.equal(0); }); + it('uses response width and height', function () { + const result = spec.interpretResponse({ body: serverResponse }, bidRequest)[0]; + expect(result.width).to.equal(serverResponse.ad.width.toString()); + expect(result.height).to.equal(serverResponse.ad.height.toString()); + }); + + it('defaults to use bidRequest sizes when width and height are not found', function () { + const { ad, jcsi, pag, thms, meta } = serverResponse + const noAdSizes = { ...ad } + delete noAdSizes.width + delete noAdSizes.height + const responseWithoutSizes = { jcsi, pag, thms, meta, ad: noAdSizes } + const request = { ...bidRequest, sizes: [[100, 200]] } + const result = spec.interpretResponse({ body: responseWithoutSizes }, request)[0]; + + expect(result.width).to.equal(request.sizes[0][0].toString()) + expect(result.height).to.equal(request.sizes[0][1].toString()) + }); + it('returns 1x1 when eligible product and size available', function () { let inscreenBidRequest = { id: 12346, diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js index 08a83ce981f..9861069f260 100644 --- a/test/spec/modules/h12mediaBidAdapter_spec.js +++ b/test/spec/modules/h12mediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/h12mediaBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; describe('H12 Media Adapter', function () { const DEFAULT_CURRENCY = 'USD'; @@ -21,6 +22,7 @@ describe('H12 Media Adapter', function () { auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f313', params: { pubid: 123321, + pubsubid: 'pubsubtestid', }, }; @@ -72,34 +74,34 @@ describe('H12 Media Adapter', function () { currency: 'EUR', netRevenue: true, ttl: 500, - bids: [{ + bid: { bidId: validBid.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad
', - usersync: [ - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} - ], meta: { advertiserId: '54321', advertiserName: 'My advertiser', advertiserDomains: ['test.com'] } - }] + }, + usersync: [ + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} + ], }; const serverResponse2 = { - bids: [{ + bid: { bidId: validBid2.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad 2
', - }] + } }; function removeElement(id) { @@ -152,6 +154,10 @@ describe('H12 Media Adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + sandbox.stub(frameElement, 'getBoundingClientRect').returns({ + left: 10, + top: 10, + }); }); afterEach(function () { @@ -186,36 +192,62 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); + expect(requestsData).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); }); it('should return empty bid size', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData2).to.deep.include({adunitSize: []}); + }); + + it('should return pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData).to.include({pubsubid: 'pubsubtestid'}); + expect(requestsData2).to.include({pubsubid: ''}); + }); - expect(requestsData.bidrequests[1]).to.deep.include({adunitSize: []}); + it('should return empty for incorrect pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidWithPub = {...validBid}; + bidWithPub.params.pubsubid = 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'; // More than 32 chars + const requests = spec.buildRequests([bidWithPub], bidderRequest); + const requestsData = requests[0].data.bidrequest; + + expect(requestsData).to.include({pubsubid: ''}); }); it('should return bid size from params', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; - expect(requestsData.bidrequests[1]).to.include({size: validBid2.params.size}); + expect(requestsData).to.include({size: ''}); + expect(requestsData2).to.include({size: validBid2.params.size}); }); it('should return GDPR info', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); + expect(requestsData2).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); }); it('should not have error on empty GDPR', function () { @@ -223,9 +255,23 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const bidderRequestWithoutGDRP = {...bidderRequest, gdprConsent: null}; const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutGDRP); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: false}); + expect(requestsData2).to.include({gdpr: false}); + }); + + it('should not have error on empty USP', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidderRequestWithoutUSP = {...bidderRequest, uspConsent: null}; + const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutUSP); + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; + + expect(requestsData).to.include({usp: false}); + expect(requestsData2).to.include({usp: false}); }); it('should create single POST', function () { @@ -233,7 +279,8 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - expect(requests.method).to.equal('POST'); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); }); }); @@ -241,42 +288,44 @@ describe('H12 Media Adapter', function () { it('should return coords', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.deep.include({coords: {x: 10, y: 10}}); + expect(requestsData).to.deep.include({coords: {x: 10, y: 10}}); }); - it('should define not iframe', function () { + it('should define iframe', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; - expect(requestsData).to.include({isiframe: false}); + expect(requestsData).to.include({isiframe: true}); + expect(requestsData2).to.include({isiframe: true}); }); it('should define visible element', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: false}); + expect(requestsData).to.include({ishidden: false}); }); it('should define invisible element', function () { createElementInvisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); it('should define hidden element', function () { createElementHidden(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); }); @@ -290,27 +339,27 @@ describe('H12 Media Adapter', function () { it('should return no bids if the response is empty', function () { const bidResponse = spec.interpretResponse({ body: [] }, { validBid }); - expect(bidResponse.length).to.equal(0); + expect(bidResponse).to.be.empty; }); it('should return valid bid responses', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse}, request); + const bidResponse = spec.interpretResponse({body: serverResponse}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid.bidId, - ad: serverResponse.bids[0].ad, + ad: serverResponse.bid.ad, mediaType: 'banner', - creativeId: serverResponse.bids[0].creativeId, - cpm: serverResponse.bids[0].cpm, - width: serverResponse.bids[0].width, - height: serverResponse.bids[0].height, + creativeId: serverResponse.bid.creativeId, + cpm: serverResponse.bid.cpm, + width: serverResponse.bid.width, + height: serverResponse.bid.height, currency: 'EUR', netRevenue: true, ttl: 500, - meta: serverResponse.bids[0].meta, + meta: serverResponse.bid.meta, pubid: validBid.params.pubid }); }); @@ -319,17 +368,17 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse2}, request); + const bidResponse = spec.interpretResponse({body: serverResponse2}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid2.bidId, - ad: serverResponse2.bids[0].ad, + ad: serverResponse2.bid.ad, mediaType: 'banner', - creativeId: serverResponse2.bids[0].creativeId, - cpm: serverResponse2.bids[0].cpm, - width: serverResponse2.bids[0].width, - height: serverResponse2.bids[0].height, - meta: serverResponse2.bids[0].meta, + creativeId: serverResponse2.bid.creativeId, + cpm: serverResponse2.bid.cpm, + width: serverResponse2.bid.width, + height: serverResponse2.bid.height, + meta: serverResponse2.bid.meta, pubid: validBid2.params.pubid, currency: DEFAULT_CURRENCY, netRevenue: DEFAULT_NET_REVENUE, diff --git a/test/spec/modules/haloIdSystem_spec.js b/test/spec/modules/haloIdSystem_spec.js new file mode 100644 index 00000000000..8b6a67adee1 --- /dev/null +++ b/test/spec/modules/haloIdSystem_spec.js @@ -0,0 +1,42 @@ +import { haloIdSubmodule, storage } from 'modules/haloIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + +describe('HaloIdSystem', function () { + describe('getId', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + it('gets a haloId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://id.halo.ad.gt/api/v1/pbhid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); + }); + + it('gets a cached haloid', function() { + const config = { + params: {} + }; + getDataFromLocalStorageStub.withArgs('auHaloId').returns('tstCachedHaloId1'); + + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'tstCachedHaloId1'}); + }); + }); +}); diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js index 69ea4bc997c..3052441a00d 100644 --- a/test/spec/modules/haloRtdProvider_spec.js +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -1,220 +1,374 @@ -import { HALOID_LOCAL_NAME, SEG_LOCAL_NAME, addSegmentData, getSegments, haloSubmodule, storage } from 'modules/haloRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; +import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, haloSubmodule, storage} from 'modules/haloRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('haloRtdProvider', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + describe('haloSubmodule', function() { it('successfully instantiates', function () { - expect(haloSubmodule.init()).to.equal(true); + expect(haloSubmodule.init()).to.equal(true); }); }); - describe('Add Segment Data', function() { - it('adds segment data', function() { - const config = { - params: { - mapSegments: { - 'appnexus': true, - 'generic': true - } - } + describe('Add Real-Time Data', function() { + it('merges ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] }; - let adUnits = [ - { - bids: [ - // bid with existing segment data in bid obj and fpd - { - bidder: 'appnexus', - fpd: { - user: { - data: [ - { - id: 'appnexus', - segment: [ - { - id: '0' - } - ] - } - ] - } - }, - params: { - user: { - segments: [0] - } - } - } - ] + const setConfigUserObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const setConfigSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } - // bids with fpd data definitions but without existing segment data - { - bids: [ - { - bidder: 'appnexus', - fpd: { - user: { - data: [ - { - id: 'appnexus' - } - ] - } - } - }, - { - bidder: 'generic', - fpd: { - user: { - data: [ - { - id: 'generic' - } - ] - } - } + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + }, + site: { + content: { + data: [setConfigSiteObj1] } - ] + } } - ]; + }); - const data = { - appnexus: [{id: '1'}, {id: '2'}, {id: '3'}], - generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] }; - addSegmentData(adUnits, data, config); + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1945' + }, + { + id: '2003' + } + ] + }; - expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '0'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '1'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '2'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', '3'); - expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [0, 1, 2, 3]); + const rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }; + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '1'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '2'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '3'); - expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [1, 2, 3]); + let ortb2Config = config.getConfig().ortb2; - expect(adUnits[1].bids[1].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[1].bids[1].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[1].bids[1].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'seg3'); - expect(adUnits[1].bids[1].segments[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[1].bids[1].segments[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[1].bids[1].segments[2]).to.have.deep.property('id', 'seg3'); + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); }); - it('allows mapper extensions and overrides', function() { - const config = { + it('allows publisher defined rtd ortb2 logic', function() { + const rtdConfig = { params: { - mapSegments: { - generic: (bid, segments) => { - bid.overrideSegments = segments; - }, - newBidder: (bid, segments) => { - bid.newBidderSegments = segments; + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + if (rtd.ortb2.user.data[0].segment[0].id == '1776') { + pbConfig.setConfig({ortb2: rtd.ortb2}); + } else { + pbConfig.setConfig({ortb2: {}}); } } } }; - let adUnits = [ - { - bids: [ {bidder: 'newBidder'}, {bidder: 'generic'} ] + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + let rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + } } - ]; + }; - const data = { - newBidder: [{id: 'nbseg1', name: 'New Bidder Segment 1'}, {id: 'nbseg2', name: 'New Bidder Segment 2'}, {id: 'nbseg3', name: 'New Bidder Segment 3'}], - generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + config.resetConfig(); + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + + const rtdUserObj2 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [{ + id: 'pubseg1' + }] }; - addSegmentData(adUnits, data, config); + rtd = { + ortb2: { + user: { + data: [rtdUserObj2] + } + } + }; - expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('id', 'nbseg1'); - expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('name', 'New Bidder Segment 1'); - expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('id', 'nbseg2'); - expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('name', 'New Bidder Segment 2'); - expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('id', 'nbseg3'); - expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('name', 'New Bidder Segment 3'); + config.resetConfig(); - expect(adUnits[0].bids[1].overrideSegments[0]).to.have.deep.property('id', 'seg1'); - expect(adUnits[0].bids[1].overrideSegments[1]).to.have.deep.property('id', 'seg2'); - expect(adUnits[0].bids[1].overrideSegments[2]).to.have.deep.property('id', 'seg3'); + pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2).to.deep.equal({}); }); - }); - describe('Get Segments', function() { - it('gets segment data from local storage cache', function() { - const config = { + it('allows publisher defined adunit logic', function() { + const rtdConfig = { params: { - segmentCache: true, - mapSegments: { - 'generic': true + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz') { + for (var k = 0; k < rtd.adBuzz.length; k++) { + bid.adBuzzData.segments.adBuzz.push(rtd.adBuzz[k]); + } + } else if (bid.bidder == 'trueBid') { + for (var k = 0; k < rtd.trueBid.length; k++) { + bid.trueBidSegments.push(rtd.trueBid[k]); + } + } + } + } } } }; - let reqBidsConfigObj = { + let bidConfig = { adUnits: [ { - bids: [{bidder: 'generic'}] + bids: [ + { + bidder: 'adBuzz', + adBuzzData: { + segments: { + adBuzz: [ + { + id: 'adBuzzSeg1' + } + ] + } + } + }, + { + bidder: 'trueBid', + trueBidSegments: [] + } + ] } ] }; - const data = { - audigent_segments: { - generic: [{id: 'seg1'}] + const rtd = { + adBuzz: [{id: 'adBuzzSeg2'}, {id: 'adBuzzSeg3'}], + trueBid: [{id: 'truebidSeg1'}, {id: 'truebidSeg2'}, {id: 'truebidSeg3'}] + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[0].id).to.equal('adBuzzSeg1'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[1].id).to.equal('adBuzzSeg2'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[2].id).to.equal('adBuzzSeg3'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[0].id).to.equal('truebidSeg1'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[1].id).to.equal('truebidSeg2'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[2].id).to.equal('truebidSeg3'); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + segmentCache: true } }; - storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + const bidConfig = {}; - getSegments(reqBidsConfigObj, () => {}, config, {}); + const rtdUserObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; - expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); }); - it('gets segment data via async request', function() { - const config = { + it('gets real-time data via async request', function() { + const setConfigSiteObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + } + + config.setConfig({ + ortb2: { + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdConfig = { params: { segmentCache: false, - mapSegments: { - 'generic': true - }, + usePubHalo: true, requestParams: { - 'publisherId': 1234 + publisherId: 'testPub1' } } }; - let reqBidsConfigObj = { - adUnits: [ + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, { - bids: [{bidder: 'generic'}] + id: 'pubseg2' } ] }; + const data = { - audigent_segments: { - generic: [{id: 'seg1'}] + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } } }; - storage.setDataInLocalStorage(HALOID_LOCAL_NAME, 'haloid'); - getSegments(reqBidsConfigObj, () => {}, config, {}); + getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHaloId1'); + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); let request = server.requests[0]; let postData = JSON.parse(request.requestBody); - expect(postData.config).to.have.deep.property('publisherId', 1234); + expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); + expect(postData.userIds).to.have.deep.property('haloId', 'testHaloId1'); request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); }); }); }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 7f104ff403d..9ba9aee9c63 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -144,26 +144,26 @@ describe('ID5 ID System', function() { expect(request.withCredentials).to.be.true; expect(requestBody.partner).to.eq(ID5_TEST_PARTNER_ID); expect(requestBody.o).to.eq('pbjs'); - expect(requestBody.pd).to.eq(''); - expect(requestBody.s).to.eq(''); - expect(requestBody.provider).to.eq(''); + expect(requestBody.pd).to.be.undefined; + expect(requestBody.s).to.be.undefined; + expect(requestBody.provider).to.be.undefined expect(requestBody.v).to.eq('$prebid.version$'); expect(requestBody.gdpr).to.exist; - expect(requestBody.gdpr_consent).to.exist - expect(requestBody.us_privacy).to.exist; + expect(requestBody.gdpr_consent).to.be.undefined; + expect(requestBody.us_privacy).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with empty signature field when no stored object', function () { + it('should call the ID5 server with no signature field when no stored object', function () { let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(requestBody.s).to.eq(''); + expect(requestBody.s).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); @@ -195,7 +195,7 @@ describe('ID5 ID System', function() { request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); - it('should call the ID5 server with empty pd field when pd config is not set', function () { + it('should call the ID5 server with no pd field when pd config is not set', function () { let id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; @@ -204,7 +204,7 @@ describe('ID5 ID System', function() { let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(requestBody.pd).to.eq(''); + expect(requestBody.pd).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); @@ -449,7 +449,7 @@ describe('ID5 ID System', function() { describe('A/B Testing', function() { const expectedDecodedObjectWithIdAbOff = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; const expectedDecodedObjectWithIdAbOn = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false } } }; - const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: 0, ext: { linkType: 0, abTestingControlGroup: true } } }; + const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: '', ext: { linkType: 0, abTestingControlGroup: true } } }; let testConfig; beforeEach(function() { @@ -505,7 +505,6 @@ describe('ID5 ID System', function() { let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); sinon.assert.notCalled(logErrorSpy); - sinon.assert.notCalled(logInfoSpy); }); }); @@ -523,7 +522,6 @@ describe('ID5 ID System', function() { testConfig.params.abTesting = testAbTestingConfig; id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); sinon.assert.notCalled(logErrorSpy); - sinon.assert.calledOnce(logInfoSpy); }); }); }); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 3c544fa8d08..a31270c86c7 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -6,19 +6,21 @@ import {getStorageManager} from '../../../src/storageManager.js'; export const storage = getStorageManager(); const pid = '14'; -const defaultConfigParams = { params: {pid: pid} }; +let defaultConfigParams; const responseHeader = {'Content-Type': 'application/json'} describe('IdentityLinkId tests', function () { let logErrorStub; beforeEach(function () { + defaultConfigParams = { params: {pid: pid} }; logErrorStub = sinon.stub(utils, 'logError'); // remove _lr_retry_request cookie before test storage.setCookie('_lr_retry_request', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); }); afterEach(function () { + defaultConfigParams = {}; logErrorStub.restore(); }); @@ -141,7 +143,7 @@ describe('IdentityLinkId tests', function () { expect(request).to.be.eq(undefined); }); - it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist', function () { + it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist and notUse3P config property was not set', function () { let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -154,4 +156,13 @@ describe('IdentityLinkId tests', function () { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should not call the LiveRamp envelope endpoint if config property notUse3P is set to true', function () { + defaultConfigParams.params.notUse3P = true; + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request).to.be.eq(undefined); + }); }); diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js new file mode 100644 index 00000000000..d14cea8cad3 --- /dev/null +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from 'modules/impactifyBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; + +var gdprData = { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true +}; + +describe('ImpactifyAdapter', function () { + describe('isBidRequestValid', function () { + let validBid = { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.appId; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.appId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.format; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.format = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not equals to screen or display', () => { + const bid = utils.deepClone(validBid); + if (bid.params.format != 'screen' && bid.params.format != 'display') { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + + it('should return false when style is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.style; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when style is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.style = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.url).to.equal(ORIGIN + AUCTIONURI); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + let bidderRequest = { + bids: [ + { + bidId: '462c08f20d428', + adUnitCode: '/19968336/header-bid-tag-1', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + bidder: 'impactify', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let expectedResponse = [ + { + id: '65820304700829014', + requestId: '462c08f20d428', + cpm: 3.40, + currency: DEFAULT_CURRENCY, + netRevenue: true, + ad: '', + width: 1, + height: 1, + hash: 'test', + expiry: 166192938, + ttl: 300, + creativeId: '97517771' + } + ]; + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }); + describe('getUserSyncs', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + let validResponse = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + it('should return empty response if server response is false', function () { + const result = spec.getUserSyncs('bad', false, gdprData); + expect(result).to.be.empty; + }); + it('should return empty response if server response is empty', function () { + const result = spec.getUserSyncs('bad', [], gdprData); + expect(result).to.be.empty; + }); + it('should append the various values if they exist', function() { + const result = spec.getUserSyncs({iframeEnabled: true}, validResponse, gdprData); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + }); + }); + + describe('On winning bid', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onBidWon(bid); + assert.ok(result); + }); + + describe('On bid Time out', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onTimeout(bid); + assert.ok(result); + }); +}) diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 4d0da695d95..f34a75ef8f3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -750,7 +750,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -767,7 +766,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -783,7 +781,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -832,7 +829,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', diff --git a/test/spec/modules/instreamTracking_spec.js b/test/spec/modules/instreamTracking_spec.js index 8c49da76ab6..8d795fec88b 100644 --- a/test/spec/modules/instreamTracking_spec.js +++ b/test/spec/modules/instreamTracking_spec.js @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { trackInstreamDeliveredImpressions } from 'modules/instreamTracking.js'; import { config } from 'src/config.js'; -import * as events from 'src/events.js'; +import events from 'src/events.js'; import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { INSTREAM, OUTSTREAM } from 'src/video.js'; diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 4b45342cb6f..7e819195b9f 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; +import {intentIqIdSubmodule, readData, FIRST_PARTY_KEY} from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; @@ -46,7 +46,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); request.respond( 200, responseHeader, @@ -55,9 +55,9 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should ignore NA and invalid responses', function () { - let resp = JSON.stringify({'RESULT': 'NA'}); - expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined); + it('should ignore NA and invalid responses in decode', function () { + // let resp = JSON.stringify({'RESULT': 'NA'}); + // expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined); expect(intentIqIdSubmodule.decode('NA')).to.equal(undefined); expect(intentIqIdSubmodule.decode('')).to.equal(undefined); expect(intentIqIdSubmodule.decode(undefined)).to.equal(undefined); @@ -68,7 +68,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(paiConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11&iiqidtype=2&iiqpcid='); request.respond( 200, responseHeader, @@ -82,7 +82,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(pcidConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&iiqidtype=2&iiqpcid='); request.respond( 200, responseHeader, @@ -96,7 +96,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); request.respond( 200, responseHeader, @@ -110,7 +110,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); request.respond( 204, responseHeader, @@ -118,7 +118,6 @@ describe('IntentIQ tests', function () { ); expect(callBackSpy.calledOnce).to.be.true; expect(request.response).to.equal(''); - expect(logErrorStub.calledOnce).to.not.be.true; }); it('should log an error and continue to callback if ajax request errors', function () { @@ -126,7 +125,7 @@ describe('IntentIQ tests', function () { let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); request.respond( 503, responseHeader, @@ -135,17 +134,48 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should log an error and continue to callback if ajax request errors', function () { + it('should ignore {RESULT: NA} in get id', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); request.respond( - 503, + 200, responseHeader, - 'Unavailable' + JSON.stringify({RESULT: 'NA'}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); + + it('should ignore NA in get id', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + 'NA' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); + + it('should parse result from json response', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({pid: 'test_pid', data: 'test_personid'}) ); expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('test_personid'); }); }); diff --git a/test/spec/modules/interactiveOffersBidAdapter_spec.js b/test/spec/modules/interactiveOffersBidAdapter_spec.js new file mode 100644 index 00000000000..8826977f34a --- /dev/null +++ b/test/spec/modules/interactiveOffersBidAdapter_spec.js @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import {spec} from 'modules/interactiveOffersBidAdapter.js'; + +describe('Interactive Offers Prebbid.js Adapter', function() { + describe('isBidRequestValid function', function() { + let bid = {bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}; + + it('returns true if all the required params are present and properly formatted', function() { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false if any if the required params is missing', function() { + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false if any if the required params is not properly formatted', function() { + bid.params = {pubid: 'abcd123', tmax: 250}; + expect(spec.isBidRequestValid(bid)).to.be.false; + bid.params = {pubid: 100, tmax: '+250'}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + describe('buildRequests function', function() { + let validBidRequests = [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; + let bidderRequest = {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://www.google.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://www.google.com'], canonicalUrl: null}}; + + it('returns a Prebid.js request object with a valid json string at the "data" property', function() { + let request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).length !== 0; + }); + }); + describe('interpretResponse function', function() { + let openRTBResponse = {body: [{cur: 'USD', id: '2052afa35febb79baa9893cc3ae8b83b89740df65fe98b1bd358dbae6e912801', seatbid: [{seat: 1493, bid: [{ext: {tagid: '227faa83f86546'}, crid: '24477', adm: '', nurl: '', adid: '1138', adomain: ['url.com'], price: '1.53', w: 300, h: 250, iurl: 'http://url.com', cat: ['IAB13-11'], id: '5507ced7a39c06942d3cb260197112ba712e4180', attr: [], impid: 1, cid: '13280'}]}], 'bidid': '0959b9d58ba71b3db3fa29dce3b117c01fc85de0'}], 'headers': {}}; + let prebidRequest = {method: 'POST', url: 'https://url.com', data: '{"id": "1aad860c-e04b-482b-acac-0da55ed491c8", "site": {"id": "url.com", "name": "url.com", "domain": "url.com", "page": "http://url.com", "ref": "http://url.com", "publisher": {"id": 100, "name": "http://url.com", "domain": "url.com"}, "content": {"language": "pt-PT"}}, "source": {"fd": 0, "tid": "1aad860c-e04b-482b-acac-0da55ed491c8", "pchain": ""}, "device": {"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36", "language": "pt-PT"}, "user": {}, "imp": [{"id":1, "secure": 0, "tagid": "227faa83f86546", "banner": {"pos": 0, "w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}}], "tmax": 300}', bidderRequest: {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://url.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://url.com'], canonicalUrl: null}}}; + + it('returns an array of Prebid.js response objects', function() { + let prebidResponses = spec.interpretResponse(openRTBResponse, prebidRequest); + expect(prebidResponses).to.not.be.empty; + }); + }); +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 442039504f8..ee3624b5b16 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -22,7 +22,11 @@ describe('invibesBidAdapter:', function () { [400, 300], [125, 125] ], - transactionId: 't1' + transactionId: 't1', + userId: { + pubcid: 'pub-cid-code', + pubProvidedId: 'pub-provided-id' + } }, { bidId: 'b2', bidder: BIDDER_CODE, @@ -284,6 +288,45 @@ describe('invibesBidAdapter:', function () { expect(request.data.purposes.split(',')[9]).to.equal('true'); }); + it('should send legitimateInterests 2 & 7', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + }, + legitimateInterests: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.li.split(',')[1] && request.data.li.split(',')[6]).to.equal('true'); + }); it('should send oi = 0 when vendorData is null', function () { let bidderRequest = { gdprConsent: { @@ -321,7 +364,6 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when vendor consents for invibes are false on tcf v2', function () { let bidderRequest = { gdprConsent: { @@ -349,6 +391,74 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(0); }); + it('should send oi = 2 when vendor consent for invibes are false and vendor legitimate interest for invibes are true on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + it('should send oi = 0 when vendor consents and legitimate interests for invibes are false on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: false}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + it('should send oi = 0 when purpose consents is null', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}}, + purpose: {} + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); it('should send oi = 2 when purpose consents weren\'t approved on tcf v2', function () { let bidderRequest = { diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 93c3a6fb7b9..cca928ff28b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('ironsourceAdapter', function () { bidderCode: 'ironsource', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -98,6 +100,22 @@ describe('ironsourceAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('should send the correct width and height', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 6d7b3a397bd..e0d45913fd9 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -26,7 +26,7 @@ describe('IndexexchangeAdapter', function () { } ] }; - var div_many_sizes = [ + const div_many_sizes = [ [300, 250], [600, 410], [336, 280], @@ -94,6 +94,62 @@ describe('IndexexchangeAdapter', function () { [600, 40], [600, 30] ]; + + const ONE_VIDEO = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + }, + size: [400, 100] + }, + sizes: [[400, 100]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const ONE_BANNER = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -355,6 +411,7 @@ describe('IndexexchangeAdapter', function () { netId: 'testnetid123', // NetId IDP: 'userIDP000', // IDP fabrickId: 'fabrickId9000', // FabrickId + uid2: {id: 'testuid2'} // UID 2.0 }; const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); @@ -392,9 +449,22 @@ describe('IndexexchangeAdapter', function () { rtiPartner: 'zeotapIdPlus' } }] + }, { + source: 'uidapi.com', + uids: [{ + // when calling createEidsArray, UID2's getValue func returns .id, which is then set in uids + id: DEFAULT_USERID_DATA.uid2.id, + ext: { + rtiPartner: 'UID2' + } + }] } ]; + const DEFAULT_USERID_BID_DATA = { + lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4' + }; + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -604,6 +674,34 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('Roundel alias adapter', function () { + const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; + const ALIAS_OPTIONS = Object.assign({ + bidderCode: 'roundel' + }, DEFAULT_OPTION); + + it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { + vaildBids.forEach((validBid) => { + const request = spec.buildRequests(validBid, ALIAS_OPTIONS); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(0); + }); + }); + + it('should build requests for mediaTypes if liveramp data is avaliable', function () { + vaildBids.forEach((validBid) => { + const cloneValidBid = utils.deepClone(validBid); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); + const payload = JSON.parse(request[0].data.r); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(1); + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + }); + }); + }); + describe('buildRequestsIdentity', function () { let request; let query; @@ -793,16 +891,16 @@ describe('IndexexchangeAdapter', function () { it('IX adapter reads supported user modules from Prebid and adds it to Video', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(4); + expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { @@ -893,7 +991,6 @@ describe('IndexexchangeAdapter', function () { }; const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; @@ -935,7 +1032,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); @@ -943,6 +1040,7 @@ describe('IndexexchangeAdapter', function () { expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[6]); }); it('IXL and Prebid are mutually exclusive', function () { @@ -964,7 +1062,6 @@ describe('IndexexchangeAdapter', function () { }; const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; @@ -983,12 +1080,39 @@ describe('IndexexchangeAdapter', function () { }); const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.have.lengthOf(6); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); + }); + }); + + describe('getUserIds', function () { + it('request should contain userId information if configured and within bid request', function () { + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { name: 'lotamePanoramaId' }, + { name: 'merkleId' }, + { name: 'parrableId' }, + ] + } + }); + + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.userId = DEFAULT_USERID_BID_DATA; + + const request = spec.buildRequests([bid], DEFAULT_OPTION)[0]; + const r = JSON.parse(request.data.r); + + expect(r.ext.ixdiag.userIds).to.be.an('array'); + expect(r.ext.ixdiag.userIds.should.include('lotamePanoramaId')); + expect(r.ext.ixdiag.userIds.should.not.include('merkleId')); + expect(r.ext.ixdiag.userIds.should.not.include('parrableId')); }); }); @@ -1057,6 +1181,84 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.sid).to.equal(sidValue); }); + it('video impression has #priceFloors floors', function () { + const bid = utils.deepClone(ONE_VIDEO[0]); + const flr = 5.5 + const floorInfo = {floor: flr, currency: 'USD'}; + bid.getFloor = function () { + return floorInfo; + } + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('banner imp has floors from #priceFloors module', function () { + const floor300x250 = 3.25 + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) + + const floorInfo = { floor: floor300x250, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.bidfloor).to.equal(floor300x250); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('ix adapter floors chosen over #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorhi + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorlow, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('x'); + }); + + it(' #priceFloors floors chosen over ix adapter floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorlow + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorhi, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('p'); + }); + it('impression should have bidFloor and bidFloorCur if configured', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -1066,6 +1268,73 @@ describe('IndexexchangeAdapter', function () { expect(impression.bidfloor).to.equal(bid.params.bidFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.ext.fl).to.equal('x'); + }); + + it('missing sizes #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.mediaTypes.banner.sizes.push([500, 400]) + + const floorInfo = { floor: 3.25, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + // called getFloor with 500 x 400 + expect(bid.getFloor.getCall(1).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(1).args[0].size[0]).to.equal(500); + expect(bid.getFloor.getCall(1).args[0].size[1]).to.equal(400); + + // both will have same floors due to mock getFloor + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(3.25); + expect(imp1.bidfloorcur).to.equal('USD'); + + const imp2 = JSON.parse(requestBidFloor.data.r).imp[1]; + expect(imp2.bidfloor).to.equal(3.25); + expect(imp2.bidfloorcur).to.equal('USD'); + }); + + it('#pricefloors inAdUnit, banner impressions should have floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const flr = 4.3 + bid.floors = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size'] + }, + values: { + 'banner|300x250': flr, + 'banner|600x500': 6.5, + 'banner|*': 7.5 + } + }; + const floorInfo = { floor: flr, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); }); it('payload without mediaType should have correct format and value', function () { @@ -1145,7 +1414,6 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; - expect(pageUrl).to.equal(expectedPageUrl); }); @@ -1492,7 +1760,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.mimes[0]).to.not.equal('video/override'); }); - it('should not add video adunit level properties in imp object if they are not whitelisted', function () { + it('should not add video adunit level properties in imp object if they are not allowlisted', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.random = true; @@ -1502,7 +1770,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.random).to.not.exist; }); - it('should add whitelisted adunit level video properties in imp object if they are not configured at params level', function () { + it('should add allowlisted adunit level video properties in imp object if they are not configured at params level', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; delete bid.params.video.protocols; @@ -1571,7 +1839,7 @@ describe('IndexexchangeAdapter', function () { expect(diagObj.ou).to.equal(1); expect(diagObj.ren).to.equal(false); expect(diagObj.mfu).to.equal(1); - expect(diagObj.allU).to.equal(1); + expect(diagObj.allu).to.equal(1); expect(diagObj.version).to.equal('$prebid.version$'); }); }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index f7beb6ba486..458e45e8ae7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -229,8 +229,8 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: mediaIdWithSegment, @@ -298,8 +298,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -345,8 +345,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -392,8 +392,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -435,19 +435,19 @@ describe('jwplayerRtdProvider', function() { }); it('should include banner ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { banner: {} }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should include outstream ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { video: { context: 'outstream' } }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should fallback to config when empty jwTargeting is defined in ad unit', function () { - const adUnit = { fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); @@ -457,7 +457,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -468,7 +468,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -577,8 +577,8 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[0] @@ -591,8 +591,8 @@ describe('jwplayerRtdProvider', function() { ] }, { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[1] @@ -658,8 +658,8 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -690,8 +690,8 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -701,7 +701,7 @@ describe('jwplayerRtdProvider', function() { }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -732,8 +732,8 @@ describe('jwplayerRtdProvider', function() { const adUnitEmptyfpd = { code: 'test_ad_unit_empty_fpd', - fpd: { - context: { + ortb2Imp: { + ext: { id: 'sthg' } }, diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 9dbcca8e331..43968bbef5a 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -134,6 +134,21 @@ describe('kargo adapter tests', function () { noAdServerCurrency = true; } + function generateGDPR(applies, haveConsent) { + var data = { + consentString: 'gdprconsentstring', + gdprApplies: applies, + }; + return data; + } + + function generateGDPRExpect(applies, haveConsent) { + return { + consent: 'gdprconsentstring', + applies: applies, + }; + } + function initializeKruxUser() { setLocalStorageItem('kxkar_user', 'rsgr9pnij'); } @@ -221,7 +236,7 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) { + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { var base = { timeout: 200, requestCount: requestCount++, @@ -299,6 +314,10 @@ describe('kargo adapter tests', function () { rawCRBLocalStorage: expectedRawCRB }; + if (expectedGDPR) { + base.userIDs['gdpr'] = expectedGDPR; + } + if (excludeUserIds === true) { base.userIDs = { crbIDs: {}, @@ -317,12 +336,16 @@ describe('kargo adapter tests', function () { return base; } - function testBuildRequests(excludeTdid, expected) { + function testBuildRequests(excludeTdid, expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var request = spec.buildRequests(clonedBids, {timeout: 200, uspConsent: '1---', foo: 'bar'}); + var payload = { timeout: 200, uspConsent: '1---', foo: 'bar' }; + if (gdpr) { + payload['gdprConsent'] = gdpr + } + var request = spec.buildRequests(clonedBids, payload); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); @@ -431,6 +454,15 @@ describe('kargo adapter tests', function () { initializeKrgCrb(); testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); }); + + it('sends gdpr consent', function () { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + }); }); describe('response handler', function() { @@ -558,8 +590,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed() { - return spec.getUserSyncs({iframeEnabled: true}); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); } function getUserSyncsWhenForbidden() { @@ -574,17 +606,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` }; } - function getSyncUrls() { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); } return syncs; } @@ -606,6 +638,21 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); }); + it('no user syncs when there is no us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + }); + + it('pass through us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + }); + + it('pass through gdpr consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js new file mode 100644 index 00000000000..725c9ece118 --- /dev/null +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -0,0 +1,694 @@ +import {expect} from 'chai'; +import {spec} from 'modules/koblerBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {getRefererInfo} from 'src/refererDetection.js'; + +function createBidderRequest(auctionId, timeout, pageUrl) { + return { + auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', + timeout: timeout || 2000, + refererInfo: { + referer: pageUrl || 'example.com' + } + }; +} + +function createValidBidRequest(params, bidId, sizes) { + return { + adUnitCode: 'adunit-code', + bidId: bidId || '22c4871113f461', + bidder: 'kobler', + bidderRequestId: '15246a574e859f', + bidRequestsCount: 1, + bidderRequestsCount: 1, + mediaTypes: { + banner: { + sizes: sizes || [[300, 250], [320, 100]] + } + }, + params: params || { + placementId: 'tpw58278' + }, + transactionTd: '04314114-15bd-4638-8664-bdb8bdc60bff' + }; +} + +describe('KoblerAdapter', function () { + describe('inherited functions', function () { + it('exists and is a function', function () { + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should not accept a request without bidId as valid', function () { + const bid = { + params: { + someParam: 'abc' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without params as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589' + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without placementId as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + params: { + someParam: 'abc' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should accept a request with bidId and placementId as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + params: { + someParam: 'abc', + placementId: '8bde0923-1409-4253-9594-495b58d931ba' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + }); + + describe('buildRequests', function () { + it('should read data from bidder request', function () { + const testUrl = 'kobler.no'; + const auctionId = '8319af54-9795-4642-ba3a-6f57d6ff9100'; + const timeout = 5000; + const validBidRequests = [createValidBidRequest()]; + const bidderRequest = createBidderRequest(auctionId, timeout, testUrl); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.tmax).to.be.equal(timeout); + expect(openRtbRequest.id).to.be.equal(auctionId); + expect(openRtbRequest.site.page).to.be.equal(testUrl); + }); + + it('should read data from valid bid requests', function () { + const firstSize = [400, 800]; + const secondSize = [450, 950]; + const sizes = [firstSize, secondSize]; + const placementId = 'tsjs86325'; + const bidId = '3a56a019-4835-4f75-811c-76fac6853a2c'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: placementId + }, + bidId, + sizes + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].id).to.be.equal(bidId); + expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[0].banner.w).to.be.equal(firstSize[0]); + expect(openRtbRequest.imp[0].banner.h).to.be.equal(firstSize[1]); + expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(2); + expect(openRtbRequest.imp[0].banner.format[0].w).to.be.equal(firstSize[0]); + expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(firstSize[1]); + expect(openRtbRequest.imp[0].banner.format[1].w).to.be.equal(secondSize[0]); + expect(openRtbRequest.imp[0].banner.format[1].h).to.be.equal(secondSize[1]); + }); + + it('should use 0x0 as default size', function () { + const validBidRequests = [ + createValidBidRequest( + undefined, + undefined, + [] + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.w).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.h).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.format[0].w).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(0); + }); + + it('should use 0 as default position', function () { + const validBidRequests = [createValidBidRequest()]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(0); + }); + + it('should read zip from valid bid requests', function () { + const zip = '700 02'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'nmah8324234', + zip: zip + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.device.geo.zip).to.be.equal(zip); + }); + + it('should read test from valid bid requests', function () { + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'zwop842799', + test: true + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.test).to.be.equal(1); + }); + + it('should read floorPrice from valid bid requests', function () { + const floorPrice = 4.343; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'oqr3224234', + floorPrice: floorPrice + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); + }); + + it('should read position from valid bid requests', function () { + const placementId = 'yzksf234592'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: placementId, + position: 1 + } + ), + createValidBidRequest( + { + placementId: placementId, + position: 2 + } + ), + createValidBidRequest( + { + placementId: placementId, + position: 3 + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(3); + expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(1); + expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[1].banner.ext.kobler.pos).to.be.equal(2); + expect(openRtbRequest.imp[1].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[2].banner.ext.kobler.pos).to.be.equal(3); + expect(openRtbRequest.imp[2].tagid).to.be.equal(placementId); + }); + + it('should read dealIds from valid bid requests', function () { + const dealIds1 = ['78214682234823']; + const dealIds2 = ['89913861235234', '27368423545328640']; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'rsl1239823', + dealIds: dealIds1 + } + ), + createValidBidRequest( + { + placementId: 'pqw234232', + dealIds: dealIds2 + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(2); + expect(openRtbRequest.imp[0].pmp.deals.length).to.be.equal(1); + expect(openRtbRequest.imp[0].pmp.deals[0].id).to.be.equal(dealIds1[0]); + expect(openRtbRequest.imp[1].pmp.deals.length).to.be.equal(2); + expect(openRtbRequest.imp[1].pmp.deals[0].id).to.be.equal(dealIds2[0]); + expect(openRtbRequest.imp[1].pmp.deals[1].id).to.be.equal(dealIds2[1]); + }); + + it('should read timeout from config', function () { + const timeout = 4000; + const validBidRequests = [createValidBidRequest()]; + // No timeout field + const bidderRequest = { + auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', + refererInfo: { + referer: 'example.com' + } + }; + config.setConfig({ + bidderTimeout: timeout + }); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.tmax).to.be.equal(timeout); + }); + + it('should read floor price using floors module', function () { + const floorPriceFor580x400 = 6.5148; + const floorPriceForAnySize = 4.2343; + const validBidRequests = [ + createValidBidRequest(undefined, '98efe127-f926-4dde-b988-db8e5dba5a76', [[580, 400]]), + createValidBidRequest(undefined, 'c7698d4a-94f4-4a6b-a928-7e1facfbf752', []) + ]; + validBidRequests.forEach(validBidRequest => { + validBidRequest.getFloor = function (params) { + let floorPrice; + if (utils.isArray(params.size) && params.size[0] === 580 && params.size[1] === 400) { + floorPrice = floorPriceFor580x400; + } else if (params.size === '*') { + floorPrice = floorPriceForAnySize + } else { + floorPrice = 0 + } + return { + currency: params.currency, + floor: floorPrice + } + } + }) + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(2); + expect(openRtbRequest.imp[0].id).to.be.equal('98efe127-f926-4dde-b988-db8e5dba5a76'); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPriceFor580x400); + expect(openRtbRequest.imp[1].id).to.be.equal('c7698d4a-94f4-4a6b-a928-7e1facfbf752'); + expect(openRtbRequest.imp[1].bidfloor).to.be.equal(floorPriceForAnySize); + }); + + it('should create whole OpenRTB request', function () { + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'pcha322364', + zip: '0015', + floorPrice: 5.6234, + position: 1, + dealIds: ['623472534328234'] + }, + '953ee65d-d18a-484f-a840-d3056185a060', + [[400, 600]] + ), + createValidBidRequest( + { + placementId: 'sdfgoi32y4', + floorPrice: 3.2543, + position: 2, + dealIds: ['92368234753283', '263845832942'] + }, + '8320bf79-9d90-4a17-87c6-5d505706a921', + [[400, 500], [200, 250], [300, 350]] + ), + createValidBidRequest( + { + placementId: 'gwms2738647', + position: 3 + }, + 'd0de713b-32e3-4191-a2df-a007f08ffe72', + [[800, 900]] + ) + ]; + const bidderRequest = createBidderRequest( + '9ff580cf-e10e-4b66-add7-40ac0c804e21', + 4500, + 'bid.kobler.no' + ); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + const expectedOpenRtbRequest = { + id: '9ff580cf-e10e-4b66-add7-40ac0c804e21', + at: 1, + tmax: 4500, + cur: ['USD'], + imp: [ + { + id: '953ee65d-d18a-484f-a840-d3056185a060', + banner: { + format: [ + { + w: 400, + h: 600 + } + ], + w: 400, + h: 600, + ext: { + kobler: { + pos: 1 + } + } + }, + tagid: 'pcha322364', + bidfloor: 5.6234, + bidfloorcur: 'USD', + pmp: { + deals: [ + { + id: '623472534328234' + } + ] + } + }, + { + id: '8320bf79-9d90-4a17-87c6-5d505706a921', + banner: { + format: [ + { + w: 400, + h: 500 + }, + { + w: 200, + h: 250 + }, + { + w: 300, + h: 350 + } + ], + w: 400, + h: 500, + ext: { + kobler: { + pos: 2 + } + } + }, + tagid: 'sdfgoi32y4', + bidfloor: 3.2543, + bidfloorcur: 'USD', + pmp: { + deals: [ + { + id: '92368234753283' + }, + { + id: '263845832942' + } + ] + } + }, + { + id: 'd0de713b-32e3-4191-a2df-a007f08ffe72', + banner: { + format: [ + { + w: 800, + h: 900 + } + ], + w: 800, + h: 900, + ext: { + kobler: { + pos: 3 + } + } + }, + tagid: 'gwms2738647', + bidfloor: 0, + bidfloorcur: 'USD', + pmp: {} + } + ], + device: { + devicetype: 2, + geo: { + zip: '0015' + } + }, + site: { + page: 'bid.kobler.no' + }, + test: 0 + }; + + expect(openRtbRequest).to.deep.equal(expectedOpenRtbRequest); + }); + }); + + describe('interpretResponse', function () { + it('should handle empty body', function () { + const responseWithEmptyBody = { + body: undefined + }; + const bids = spec.interpretResponse(responseWithEmptyBody) + + expect(bids.length).to.be.equal(0); + }); + + it('should generate bids from OpenRTB response', function () { + const responseWithTwoBids = { + body: { + seatbid: [ + { + bid: [ + { + impid: '6194ddef-89a4-404f-9efd-6b718fc23308', + price: 7.981, + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&asp=${AD_SERVER_PRICE}', + crid: 'edea9b03-3a57-41aa-9c00-abd673e22006', + dealid: '', + w: 320, + h: 250, + adm: '', + adomain: [ + 'https://kobler.no' + ] + }, + { + impid: '2ec0b40f-d3ca-4ba5-8ce3-48290565690f', + price: 6.71234, + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&asp=${AD_SERVER_PRICE}', + crid: 'fa2d5af7-2678-4204-9023-44c526160742', + dealid: '2783483223432342', + w: 580, + h: 400, + adm: '', + adomain: [ + 'https://bid.kobler.no' + ] + } + ] + } + ], + cur: 'USD' + } + }; + const bids = spec.interpretResponse(responseWithTwoBids) + + const expectedBids = [ + { + requestId: '6194ddef-89a4-404f-9efd-6b718fc23308', + cpm: 7.981, + currency: 'USD', + width: 320, + height: 250, + creativeId: 'edea9b03-3a57-41aa-9c00-abd673e22006', + dealId: '', + netRevenue: true, + ttl: 600, + ad: '', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&asp=${AD_SERVER_PRICE}', + meta: { + advertiserDomains: [ + 'https://kobler.no' + ] + } + }, + { + requestId: '2ec0b40f-d3ca-4ba5-8ce3-48290565690f', + cpm: 6.71234, + currency: 'USD', + width: 580, + height: 400, + creativeId: 'fa2d5af7-2678-4204-9023-44c526160742', + dealId: '2783483223432342', + netRevenue: true, + ttl: 600, + ad: '', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&asp=${AD_SERVER_PRICE}', + meta: { + advertiserDomains: [ + 'https://bid.kobler.no' + ] + } + } + ]; + expect(bids).to.deep.equal(expectedBids); + }); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function () { + spec.onBidWon({}); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should not trigger pixel if nurl is empty', function () { + spec.onBidWon({ + nurl: '' + }); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should trigger pixel with replaced nurl if nurl is not empty', function () { + spec.onBidWon({ + cpm: 8.341, + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&asp=${AD_SERVER_PRICE}', + adserverTargeting: { + hb_pb: 8 + } + }); + + expect(utils.triggerPixel.callCount).to.be.equal(1); + expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( + 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&asp=8' + ); + }); + }); + + describe('onTimeout', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if timeout data is not array', function () { + spec.onTimeout(null); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should not trigger pixel if timeout data is empty', function () { + spec.onTimeout([]); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should trigger pixel with query parameters if timeout data not empty', function () { + spec.onTimeout([ + { + adUnitCode: 'adunit-code', + auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', + bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', + timeout: 100, + params: [ + { + placementId: 'xrwg62731', + } + ], + }, + { + adUnitCode: 'adunit-code-2', + auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', + bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', + timeout: 100, + params: [ + { + placementId: 'bc482234', + } + ], + } + ]); + + expect(utils.triggerPixel.callCount).to.be.equal(2); + expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( + 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + + 'placement_id=xrwg62731&page_url=' + encodeURIComponent(getRefererInfo().referer) + ); + expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( + 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + + 'placement_id=bc482234&page_url=' + encodeURIComponent(getRefererInfo().referer) + ); + }); + }); +}); diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a22f5650e50..a3a70a39731 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -23,6 +23,7 @@ describe('lemmaBidAdapter', function() { pubId: 1001, adunitId: 1, currency: 'AUD', + bidFloor: 1.3, geo: { lat: '12.3', lon: '23.7', @@ -46,6 +47,7 @@ describe('lemmaBidAdapter', function() { params: { pubId: 1001, adunitId: 1, + bidFloor: 1.3, video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, @@ -161,6 +163,7 @@ describe('lemmaBidAdapter', function() { expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id expect(data.imp[0].tagid).to.equal('1'); // tagid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); }); it('Request params check without mediaTypes object', function() { var bidRequests = [{ @@ -192,6 +195,7 @@ describe('lemmaBidAdapter', function() { expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id expect(data.imp[0].tagid).to.equal(undefined); // tagid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); }); it('Request params multi size format object check', function() { var bidRequests = [{ @@ -309,6 +313,77 @@ describe('lemmaBidAdapter', function() { expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); }); + describe('setting imp.floor using floorModule', function() { + /* + Use the minimum value among floor from floorModule per mediaType + If params.bidFloor is set then take max(floor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function(req) { + return floorModuleTestData[req.mediaType]; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'AUD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'AUD', + 'floor': 2.00 + } + }; + newRequest = utils.deepClone(bidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function() { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('bidFloor is not passed, use minimum from floorModule', function() { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('bidFloor is passed as 1, use min of floorModule as it is highest', function() { + newRequest[0].params.bidFloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + }); describe('Response checking', function() { it('should check for valid response values', function() { var request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3b4e4b9d9a7..f1de2f3bf93 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -36,7 +36,7 @@ describe('LiveIntentId', function() { resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function() { + it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -47,12 +47,16 @@ describe('LiveIntentId', function() { submoduleCallback(callBackSpy); let request = server.requests[1]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + const response = { + unifiedId: 'a_unified_id', + segments: [123, 234] + } request.respond( 200, responseHeader, - JSON.stringify({}) + JSON.stringify(response) ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith(response)).to.be.true; }); it('should fire an event when getId', function() { @@ -131,11 +135,10 @@ describe('LiveIntentId', function() { let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( - 200, - responseHeader, - JSON.stringify({}) + 204, + responseHeader ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith({})).to.be.true; }); it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 7983e8fbb0b..9435d9131fa 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -625,6 +625,79 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + it('should pass us privacy parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + let testRequest = clone(bidderRequest); + testRequest.uspConsent = '1---'; + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + usPrivacy: '1---', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass coppa parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'coppa') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + coppa: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + it('should pass no cookie support', function() { sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => false); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); @@ -781,61 +854,45 @@ describe('Livewrapped adapter tests', function () { }); }); - it('should make use of Id5-Id if available', function() { + it('should make use of user ids if available', function() { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); let testbidRequest = clone(bidderRequest); delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.id5id = { uid: 'id5-user-id' }; - let result = spec.buildRequests(testbidRequest.bids, testbidRequest); - let data = JSON.parse(result.data); - - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'id5-sync.com', - 'uids': [{ - 'id': 'id5-user-id', - 'atype': 1 - }] - }]); - }); - - it('should make use of publisher common Id if available', function() { - sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); - sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); - let testbidRequest = clone(bidderRequest); - delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.pubcid = 'publisher-common-id'; - let result = spec.buildRequests(testbidRequest.bids, testbidRequest); - let data = JSON.parse(result.data); - - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'pubcid.org', - 'uids': [{ - 'id': 'publisher-common-id', - 'atype': 1 - }] - }]); - }); + testbidRequest.bids[0].userIdAsEids = [ + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-id', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + }] + }, + { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'publisher-common-id', + 'atype': 1 + }] + }, + { + 'source': 'sharedid.org', + 'uids': [{ + 'id': 'sharedid', + 'atype': 1, + 'ext': { + 'third': 'sharedid' + } + }] + } + ]; - it('should make use of criteoId if available', function() { - sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); - sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); - let testbidRequest = clone(bidderRequest); - delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.criteoId = 'criteo-id'; let result = spec.buildRequests(testbidRequest.bids, testbidRequest); let data = JSON.parse(result.data); - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'criteo.com', - 'uids': [{ - 'id': 'criteo-id', - 'atype': 1 - }] - }]); + expect(data.rtbData.user.ext.eids).to.deep.equal(testbidRequest.bids[0].userIdAsEids); }); it('should send schain object if available', function() { diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index c4c06630a2b..6eb8dc1c043 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -19,7 +19,23 @@ describe('LogicadAdapter', function () { banner: { sizes: [[300, 250], [300, 600]] } - } + }, + userId: { + sharedid: { + id: 'fakesharedid', + third: 'fakesharedid' + } + }, + userIdAsEids: [{ + source: 'sharedid.org', + uids: [{ + id: 'fakesharedid', + atype: 1, + ext: { + third: 'fakesharedid' + } + }] + }] }]; const nativeBidRequests = [{ bidder: 'logicad', @@ -45,7 +61,23 @@ describe('LogicadAdapter', function () { required: true } } - } + }, + userId: { + sharedid: { + id: 'fakesharedid', + third: 'fakesharedid' + } + }, + userIdAsEids: [{ + source: 'sharedid.org', + uids: [{ + id: 'fakesharedid', + atype: 1, + ext: { + third: 'fakesharedid' + } + }] + }] }]; const bidderRequest = { refererInfo: { @@ -141,6 +173,11 @@ describe('LogicadAdapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.equal('https://pb.ladsp.com/adrequest/prebid'); expect(request.data).to.exist; + + const data = JSON.parse(request.data); + expect(data.auctionId).to.equal('18fd8b8b0bd757'); + expect(data.eids[0].source).to.equal('sharedid.org'); + expect(data.eids[0].uids[0].id).to.equal('fakesharedid'); }); }); diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index b4c2fe68f34..cf074b0f3d6 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,11 +1,41 @@ -import {spec} from '../../../modules/marsmediaBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import * as sinon from 'sinon'; +import { spec } from 'modules/marsmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -var r1adapter = spec; +var marsAdapter = spec; describe('marsmedia adapter tests', function () { + let element, win; + 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 + }; this.defaultBidderRequest = { 'refererInfo': { 'referer': 'Reference Page', @@ -15,28 +45,40 @@ describe('marsmedia adapter tests', function () { ] } }; + + this.defaultBidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('Unit-Code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); }); describe('Verify 1.0 POST Banner Bid Request', function () { it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -52,11 +94,11 @@ describe('marsmedia adapter tests', function () { expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); }); - it('interpretResponse works', function() { + /* it('interpretResponse works', function() { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-0', + 'impid': 'Unit-Code', 'w': 300, 'h': 250, 'adm': '
My Compelling Ad
', @@ -67,7 +109,7 @@ describe('marsmedia adapter tests', function () { ] }; - var bannerBids = r1adapter.interpretResponse(bidList); + var bannerBids = marsAdapter.interpretResponse(bidList); expect(bannerBids.length).to.equal(1); const bid = bannerBids[0]; @@ -78,7 +120,7 @@ describe('marsmedia adapter tests', function () { expect(bid.netRevenue).to.equal(true); expect(bid.cpm).to.equal(1.0); expect(bid.ttl).to.equal(350); - }); + }); */ }); describe('Verify POST Video Bid Request', function() { @@ -95,7 +137,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'sizes': [ [300, 250] ], @@ -107,7 +149,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -132,7 +174,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': 'https://example.com/', 'adomain': [ @@ -147,7 +189,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -166,7 +208,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': '', 'adomain': [ @@ -181,7 +223,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -199,26 +241,6 @@ describe('marsmedia adapter tests', function () { describe('misc buildRequests', function() { it('should send GDPR Consent data to Marsmedia tag', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-3', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var consentString = 'testConsentString'; var gdprBidderRequest = this.defaultBidderRequest; gdprBidderRequest.gdprConsent = { @@ -226,13 +248,50 @@ describe('marsmedia adapter tests', function () { 'consentString': consentString }; - var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, gdprBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.user.ext.consent).to.equal(consentString); expect(openrtbRequest.regs.ext.gdpr).to.equal(true); }); + it('should have CCPA Consent if defined', function () { + const ccpaBidderRequest = this.defaultBidderRequest; + ccpaBidderRequest.uspConsent = '1YYN'; + const bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, ccpaBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should submit coppa if set in config', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should process floors module if available', function() { + const floorBidderRequest = this.defaultBidRequestList; + const floorInfo = { + currency: 'USD', + floor: 1.20 + }; + floorBidderRequest[0].getFloor = () => floorInfo; + const request = marsAdapter.buildRequests(floorBidderRequest, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(1.20); + }); + + it('should have 0 bidfloor value', function() { + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(0); + }); + it('prefer 2.0 sizes', function () { var bidRequestList = [ { @@ -245,7 +304,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 600]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'sizes': [[300, 250]], 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', @@ -255,7 +314,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); @@ -274,7 +333,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -283,7 +342,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -297,7 +356,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -306,7 +365,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -320,7 +379,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -329,35 +388,15 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); it('dnt is correctly set to 1', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var dntStub = sinon.stub(utils, 'getDNT').returns(1); - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); dntStub.restore(); @@ -378,7 +417,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['600', '300'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -387,7 +426,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.equal(600); @@ -407,7 +446,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['badWidth', 'badHeight'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -416,7 +455,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -435,7 +474,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -444,7 +483,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -453,52 +492,74 @@ describe('marsmedia adapter tests', function () { it('should return empty site data when refererInfo is missing', function() { delete this.defaultBidderRequest.refererInfo; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); expect(openrtbRequest.site.page).to.equal(''); expect(openrtbRequest.site.ref).to.equal(''); }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); + }); + }); + + 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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); }); it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { this.defaultBidderRequest.refererInfo.stack = []; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); @@ -508,24 +569,7 @@ describe('marsmedia adapter tests', function () { it('should secure correctly', function() { this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].secure).to.equal(1); @@ -551,9 +595,12 @@ describe('marsmedia adapter tests', function () { 'params': { 'zoneId': 9999 }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -563,7 +610,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); @@ -571,7 +618,7 @@ describe('marsmedia adapter tests', function () { describe('misc interpretResponse', function () { it('No bid response', function() { - var noBidResponse = r1adapter.interpretResponse({ + var noBidResponse = marsAdapter.interpretResponse({ 'body': '' }); expect(noBidResponse.length).to.equal(0); @@ -589,22 +636,22 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'bannerDiv' + 'adUnitCode': 'Unit-Code' }; it('should return true when required params found', function () { - expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId missing', function () { delete bid.params.zoneId; - expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(false); }); }); describe('getUserSyncs', function () { it('returns an empty string', function () { - expect(r1adapter.getUserSyncs()).to.deep.equal([]); + expect(marsAdapter.getUserSyncs()).to.deep.equal([]); }); }); diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js new file mode 100644 index 00000000000..a4a87ce113f --- /dev/null +++ b/test/spec/modules/mass_spec.js @@ -0,0 +1,151 @@ +import { expect } from 'chai'; +import { + init, + addBidResponseHook, + addListenerOnce, + isMassBid, + useDefaultMatch, + useDefaultRender, + updateRenderers, + listenerAdded, + isEnabled +} from 'modules/mass'; +import { logInfo } from 'src/utils.js'; + +// mock a MASS bid: +const mockedMassBids = [ + { + bidder: 'ix', + bidId: 'mass-bid-1', + requestId: 'mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, + { + bidder: 'ix', + bidId: 'mass-bid-2', + requestId: 'mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: { + mass: true + } + }, +]; + +// mock non-MASS bids: +const mockedNonMassBids = [ + { + bidder: 'ix', + bidId: 'non-mass-bid-1', + requstId: 'non-mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: '', + meta: { + mass: true + } + }, + { + bidder: 'ix', + bidId: 'non-mass-bid-2', + requestId: 'non-mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, +]; + +// mock bidder request: +const mockedBidderRequest = { + bidderCode: 'ix', + bidderRequestId: 'bidder-request-id-1' +}; + +const noop = function() {}; + +describe('MASS Module', function() { + let bidderRequest = Object.assign({}, mockedBidderRequest); + + it('should be enabled by default', function() { + expect(isEnabled).to.equal(true); + }); + + it('can be disabled', function() { + init({enabled: false}); + expect(isEnabled).to.equal(false); + }); + + it('should only affect MASS bids', function() { + init({renderUrl: 'https://...'}); + mockedNonMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should only update the ad markup field', function() { + init({renderUrl: 'https://...'}); + mockedMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid.ad).to.not.equal(originalBid.ad); + + delete bid.ad; + delete originalBid.ad; + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should add a message listener', function() { + addListenerOnce(); + expect(listenerAdded).to.equal(true); + }); + + it('should support custom renderers', function() { + init({ + renderUrl: 'https://...', + custom: [ + { + dealIdPattern: /abc/, + render: function() {} + } + ] + }); + + const renderers = updateRenderers(); + + expect(renderers.length).to.equal(2); + }); + + it('should match bids by deal ID with the default matcher', function() { + const match = useDefaultMatch(/abc/); + + expect(match({dealId: 'abc'})).to.equal(true); + expect(match({dealId: 'xyz'})).to.equal(false); + }); + + it('should have a default renderer', function() { + const render = useDefaultRender('https://example.com/render.js', 'abc'); + render({}); + + expect(window.abc.loaded).to.equal(true); + expect(window.abc.queue.length).to.equal(1); + }); +}); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 3c18cfe0be7..796be3420e8 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -165,6 +165,16 @@ describe('MediaSquare bid adapter tests', function () { expect(syncs[0]).to.have.property('type').and.to.equal('image'); expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); }); + it('Verifies user sync with no bid response', function() { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); + it('Verifies user sync with no bid body response', function() { + var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); it('Verifies native in bid response', function () { const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); BID_RESPONSE.body.responses[0].native = {'title': 'native title'}; diff --git a/test/spec/modules/multibid_spec.js b/test/spec/modules/multibid_spec.js new file mode 100644 index 00000000000..e849392ee4b --- /dev/null +++ b/test/spec/modules/multibid_spec.js @@ -0,0 +1,845 @@ +import {expect} from 'chai'; +import { + validateMultibid, + adjustBidderRequestsHook, + addBidResponseHook, + resetMultibidUnits, + sortByMultibid, + targetBidPoolHook, + resetMultiConfig +} from 'modules/multibid/index.js'; +import {parse as parseQuery} from 'querystring'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; + +describe('multibid adapter', function () { + let bidArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 75, + 'originalCpm': 75, + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA', + }]; + let bidCacheArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 66, + 'originalCpm': 66, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 38, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 38, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }]; + let bidArrayAlt = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 29, + 'originalCpm': 29, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderB', + 'cpm': 3, + 'requestId': '7g8h5j45l7654i', + 'originalCpm': 3, + 'bidder': 'bidderB' + }, { + 'bidderCode': 'bidderC', + 'cpm': 12, + 'requestId': '9d7f4h56t6483u', + 'originalCpm': 12, + 'bidder': 'bidderC' + }]; + let bidderRequests = [{ + 'bidderCode': 'bidderA', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderA', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }, { + 'bidderCode': 'bidderB', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderB', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }]; + + afterEach(function () { + config.resetConfig(); + resetMultiConfig(); + resetMultibidUnits(); + }); + + describe('adjustBidderRequestsHook', function () { + let result; + let callbackFn = function (bidderRequests) { + result = bidderRequests; + }; + + beforeEach(function() { + result = null; + }); + + it('does not modify bidderRequest when no multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + adjustBidderRequestsHook(callbackFn, bidRequests); + + expect(result).to.not.equal(null); + expect(result).to.deep.equal(bidRequests); + }); + + it('does modify bidderRequest when multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does modify bidderRequest when multibid config exists using bidders array', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidders: ['bidderA'], maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does only modifies bidderRequest when multibid config exists for bidder', function () { + let bidRequests = [{...bidderRequests[0]}, {...bidderRequests[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}, {...bidderRequests[1]}]); + + expect(result).to.not.equal(null); + expect(result[0]).to.not.deep.equal(bidRequests[0]); + expect(result[0].bidLimit).to.equal(2); + expect(result[1]).to.deep.equal(bidRequests[1]); + expect(result[1].bidLimit).to.equal(undefined); + }); + }); + + describe('addBidResponseHook', function () { + let result; + let callbackFn = function (adUnitCode, bid) { + result = { + 'adUnitCode': adUnitCode, + 'bid': bid + }; + }; + + beforeEach(function() { + result = null; + }); + + it('adds original bids and does not modify', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + }); + + it('modifies and adds both bids based on multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + + delete bids[1].requestId; + delete result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + }); + + it('only modifies bids defined in the multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderB', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderB', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[2]); + }); + + it('only modifies and returns bids under limit for a specifc bidder in the multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('if no prefix in multibid configuration, modifies and returns bids under limit without preifx property', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].originalBidder = 'bidderA'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('does not include extra bids if cpm is less than floor value', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 65, + floorValue: 65, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.equal(null); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderB'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[3]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderC'); + expect(result.bid.targetingBidder).to.equal(undefined); + }); + + it('does include extra bids if cpm is not less than floor value', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 25, + floorValue: 25, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal('bidA2'); + }); + }); + + describe('targetBidPoolHook', function () { + let result; + let bidResult; + let callbackFn = function (bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + result = { + 'bidsReceived': bidsReceived, + 'adUnitBidLimit': adUnitBidLimit, + 'hasModified': hasModified + }; + }; + let bidResponseCallback = function (adUnitCode, bid) { + bidResult = bid; + }; + + beforeEach(function() { + result = null; + bidResult = null; + }); + + it('it does not run filter on bidsReceived if no multibid configuration found', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(false); + }); + + it('it does filter on bidsReceived if multibid configuration found with no prefix', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + bids.pop(); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(1); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it sorts and creates dynamic alias on bidsReceived if multibid configuration found with prefix', function () { + let modifiedBids = [{...bidArray[1]}, {...bidArray[0]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal([modifiedBids[1], modifiedBids[0]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it sorts by cpm treating dynamic alias as unique bid when no bid limit defined', function () { + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[0], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[0].cpm).to.equal(52); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].cpm).to.equal(29); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidder).to.equal('bidderC'); + expect(result.bidsReceived[2].cpm).to.equal(12); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.bidsReceived[3].bidder).to.equal('bidderB'); + expect(result.bidsReceived[3].cpm).to.equal(3); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it should filter out dynamic bid when bid limit is less than unique bid pool', function () { + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm, 3); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(3); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(3); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it should collect all bids from auction and bid cache then sort and filter', function () { + config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); + + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + let bidPool = [].concat.apply(modifiedBids, [{...bidCacheArray[0]}, {...bidCacheArray[1]}]); + + expect(bidPool.length).to.equal(6); + + targetBidPoolHook(callbackFn, bidPool, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([bidPool[4], bidPool[3], bidPool[2], bidPool[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + }); + + describe('validate multibid', function () { + it('should fail validation for missing bidder name in entry', function () { + let conf = [{maxBids: 1}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should pass validation on all multibid entries', function () { + let conf = [{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(true); + }); + + it('should fail validation for maxbids less than 1 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should fail validation for maxbids greater than 9 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 10}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should add multbid entries to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 1}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + + it('should modify multbid entries and add to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 9}]); + }); + + it('should filter multbid entry and add modified to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf.length).to.equal(1); + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + }); + + describe('sort multibid', function () { + it('should not alter order', function () { + let bids = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + + it('should sort dynamic alias bidders to end', function () { + let bids = [{ + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + }); +}); diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js new file mode 100644 index 00000000000..e1132bf1b74 --- /dev/null +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -0,0 +1,227 @@ +import { expect } from 'chai' +import { spec } from 'modules/nativoBidAdapter.js' +// import { newBidder } from 'src/adapters/bidderFactory.js' +// import * as bidderFactory from 'src/adapters/bidderFactory.js' +// import { deepClone } from 'src/utils.js' +// import { config } from 'src/config.js' + +describe('nativoBidAdapterTests', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + } + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid2 = Object.assign({}, bid) + delete bid2.params + bid2.params = {} + expect(spec.isBidRequestValid(bid2)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + it('url should contain query string parameters', function () { + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + + expect(request.url).to.exist + expect(request.url).to.be.a('string') + + expect(request.url).to.include('?') + expect(request.url).to.include('ntv_url') + expect(request.url).to.include('ntv_ptd') + }) + }) +}) + +describe('interpretResponse', function () { + let response = { + id: '126456', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1F254428-AB11-4D5E-9887-567B3F952CA5', + cpm: 3.569, + currency: 'USD', + width: 300, + height: 250, + creativeId: '1060_72_6760217', + dealId: 'f70362ac-f3cf-4225-82a5-948b690927a6', + netRevenue: true, + ttl: 360, + ad: '', + meta: { + advertiserDomains: ['test.com'], + }, + }, + ] + + let bidderRequest = { + id: 123456, + bids: [ + { + params: { + placementId: 1 + } + }, + ], + } + + // mock + spec.getRequestId = () => 123456 + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(Object.keys(result[0])).to.have.deep.members( + Object.keys(expectedResponse[0]) + ) + }) + + it('handles nobid responses', function () { + let response = {} + let bidderRequest + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(result.length).to.equal(0) + }) +}) + +describe('getUserSyncs', function () { + const response = [ + { + body: { + cur: 'USD', + id: 'a136dbd8-4387-48bf-b8e4-ff9c1d6056ee', + seatbid: [ + { + bid: [{}], + seat: 'seat_0', + syncUrls: [ + { + type: 'image', + url: 'pixel-tracker-test-url/?{GDPR_params}', + }, + { + type: 'iframe', + url: 'iframe-tracker-test-url/?{GDPR_params}', + }, + ], + }, + ], + }, + }, + ] + + const gdprConsent = { + gdprApplies: true, + consentString: '111111' + } + + const uspConsent = { + uspConsent: '1YYY' + } + + it('Returns empty array if no supported user syncs', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(0) + }) + + it('Returns valid iframe user sync', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('iframe') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) + + it('Returns valid URL and type', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('image') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) +}) diff --git a/test/spec/modules/nextrollIdSystem_spec.js b/test/spec/modules/nextrollIdSystem_spec.js new file mode 100644 index 00000000000..d89c7fe3c98 --- /dev/null +++ b/test/spec/modules/nextrollIdSystem_spec.js @@ -0,0 +1,56 @@ +import { nextrollIdSubmodule, storage } from 'modules/nextrollIdSystem.js'; + +const LS_VALUE = `{ + "AdID":{"id":"adid","key":"AdID"}, + "AdID:1002": {"id":"adid","key":"AdID:1002","value":"id_value"}}`; + +describe('NextrollId module', function () { + let sandbox = sinon.sandbox.create(); + let hasLocalStorageStub; + let getLocalStorageStub; + + beforeEach(function() { + hasLocalStorageStub = sandbox.stub(storage, 'hasLocalStorage'); + getLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + sandbox.restore(); + }) + + const testCases = [ + { + expect: { + id: {nextrollId: 'id_value'}, + }, + params: {partnerId: '1002'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '1003'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: ''}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '102'}, + localStorage: undefined + }, + { + expect: {id: undefined}, + params: undefined, + localStorage: undefined + } + ] + testCases.forEach( + (testCase, i) => it(`getId() (TC #${i}) should return the nextroll id if it exists`, function () { + getLocalStorageStub.withArgs('dca0.com').returns(testCase.localStorage); + const id = nextrollIdSubmodule.getId({params: testCase.params}); + expect(id).to.be.deep.equal(testCase.expect); + })) +}); diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).to.have.length(40); + }); + + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index bc37ea15255..3f5304dce0a 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -1,10 +1,5 @@ -import { - expect -} from 'chai'; -import { - spec -} from 'modules/oneVideoBidAdapter.js'; -import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import { spec } from 'modules/oneVideoBidAdapter.js'; describe('OneVideoBidAdapter', function () { let bidRequest; @@ -221,7 +216,7 @@ describe('OneVideoBidAdapter', function () { const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; - const VERSION = '3.0.5'; + const VERSION = '3.1.0'; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); @@ -387,37 +382,37 @@ describe('OneVideoBidAdapter', function () { bidRequest.params.video.content = null; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.undefined; + expect(data.site.content).to.be.undefined; }); it('should not accept content object if value is is Array ', function () { bidRequest.params.video.content = []; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.undefined; + expect(data.site.content).to.be.undefined; }); it('should not accept content object if value is Number ', function () { bidRequest.params.video.content = 123456; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.undefined; + expect(data.site.content).to.be.undefined; }); it('should not accept content object if value is String ', function () { bidRequest.params.video.content = 'keyValuePairs'; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.undefined; + expect(data.site.content).to.be.undefined; }); it('should not accept content object if value is Boolean ', function () { bidRequest.params.video.content = true; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.undefined; + expect(data.site.content).to.be.undefined; }); it('should accept content object if value is Object ', function () { bidRequest.params.video.content = {}; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.a('object'); + expect(data.site.content).to.be.a('object'); }); it('should not append unsupported content object keys', function () { @@ -428,7 +423,7 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.empty; + expect(data.site.content).to.be.empty; }); it('should not append content string parameters if value is not string ', function () { @@ -443,8 +438,8 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.a('object'); - expect(data.imp[0].content).to.be.empty + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty }); it('should not append content Number parameters if value is not Number ', function () { bidRequest.params.video.content = { @@ -456,8 +451,8 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.a('object'); - expect(data.imp[0].content).to.be.empty + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty }); it('should not append content Array parameters if value is not Array ', function () { bidRequest.params.video.content = { @@ -465,8 +460,8 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.a('object'); - expect(data.imp[0].content).to.be.empty + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty }); it('should not append content ext if value is not Object ', function () { bidRequest.params.video.content = { @@ -474,8 +469,8 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.be.a('object'); - expect(data.imp[0].content).to.be.empty + expect(data.site.content).to.be.a('object'); + expect(data.site.content).to.be.empty }); it('should append supported parameters if value match validations ', function () { bidRequest.params.video.content = { @@ -498,11 +493,103 @@ describe('OneVideoBidAdapter', function () { }; const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - expect(data.imp[0].content).to.deep.equal(bidRequest.params.video.content); + expect(data.site.content).to.deep.equal(bidRequest.params.video.content); }); }); }); + describe('price floor module validations', function () { + beforeEach(function () { + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values[floorObj.mediaType + '|640x480'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + } + }); + + it('should get bidfloor from getFloor method', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.floors = { + currency: 'EUR', + values: { + 'video|640x480': 5.55 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + + it('should use adUnit/module currency & floor instead of bid.params.bidfloor', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.params.bidfloor = 3.33; + bidRequest.floors = { + currency: 'EUR', + values: { + 'video|640x480': 5.55 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + + it('should load banner instead of video floor when DAP is active bid.params.video.display = 1', function () { + bidRequest.params.video.display = 1; + bidRequest.params.cur = 'EUR'; + bidRequest.mediaTypes = { + banner: { + sizes: [ + [640, 480] + ] + } + }; + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 2.22, + 'video|640x480': 9.99 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(2.22); + }) + + it('should load video floor when multi-format adUnit is present', function () { + bidRequest.params.cur = 'EUR'; + bidRequest.mediaTypes.banner = { + sizes: [ + [640, 480] + ] + }; + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 2.22, + 'video|640x480': 9.99 + } + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(9.99); + }) + }) + describe('spec.interpretResponse', function () { it('should return no bids if the response is not valid', function () { const bidResponse = spec.interpretResponse({ @@ -567,7 +654,6 @@ describe('OneVideoBidAdapter', function () { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: serverResponse.seatbid[0].bid[0].price, - adId: serverResponse.seatbid[0].bid[0].adid, creativeId: serverResponse.seatbid[0].bid[0].crid, vastXml: serverResponse.seatbid[0].bid[0].adm, width: 640, diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 121f8e76a07..7391c8826bc 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -1042,14 +1043,24 @@ describe('OpenxAdapter', function () { const EXAMPLE_DATA_BY_ATTR = { britepoolid: '1111-britepoolid', criteoId: '1111-criteoId', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + fabrickId: '1111-fabrickid', + haloId: '1111-haloid', id5id: {uid: '1111-id5id'}, idl_env: '1111-idl_env', + IDP: '1111-zeotap-idplusid', + idxId: '1111-idxid', + intentIqId: '1111-intentiqid', lipb: {lipbid: '1111-lipb'}, + lotamePanoramaId: '1111-lotameid', + merkleId: '1111-merkleid', netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, pubcid: '1111-pubcid', + quantcastId: '1111-quantcastid', + sharedId: '1111-sharedid', + tapadId: '111-tapadid', tdid: '1111-tdid', + verizonMediaId: '1111-verizonmediaid', }; // generates the same set of tests for each id provider @@ -1087,9 +1098,6 @@ describe('OpenxAdapter', function () { let userIdValue; // handle cases where userId key refers to an object switch (userIdProviderKey) { - case 'digitrustid': - userIdValue = EXAMPLE_DATA_BY_ATTR.digitrustid.data.id; - break; case 'lipb': userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; break; @@ -1187,6 +1195,7 @@ describe('OpenxAdapter', function () { let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); expect(getFloorSpy.args[0][0].currency).to.equal('USD'); }); @@ -1205,6 +1214,7 @@ describe('OpenxAdapter', function () { let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); }); }) @@ -1345,6 +1355,87 @@ describe('OpenxAdapter', function () { }); }); }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + function makeBidWithFloorInfo(floorInfo) { + return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), + { + getFloor: () => { + return floorInfo; + } + }); + } + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const floors = [9.99, 18.881]; + const bidRequests = floors.map(floor => { + return makeBidWithFloorInfo({ + currency: 'AUS', + floor: floor + }); + }); + const request = spec.buildRequests(bidRequests, mockBidderRequest); + + expect(request[0].data.aumfs).to.exist; + expect(request[0].data.aumfs).to.equal('9990'); + expect(request[1].data.aumfs).to.exist; + expect(request[1].data.aumfs).to.equal('18881'); + }); + + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + }); + + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + }); + }) + }) }); describe('buildRequest for multi-format ad', function () { @@ -1479,7 +1570,11 @@ describe('OpenxAdapter', function () { expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); }); - it('should return a brand ID', function () { + it('should return an adomain', function () { + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + + it('should return a dsp ID', function () { expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); }); }); @@ -1759,6 +1854,13 @@ describe('OpenxAdapter', function () { expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); }); + it('should return correct bid response with MediaType and deal_id', function () { + const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; + const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); + const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); + expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); + }); + it('should handle nobid responses for bidRequests with MediaTypes', function () { const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b5b76ce3fde --- /dev/null +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -0,0 +1,40 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' + +const AD_UNIT_CODE = 'demo-adunit-1'; +const PUBLISHER_CONFIG = { + pubId: 'optimon_test', + pubAdxAccount: 123456789, + pubTimezone: 'Asia/Jerusalem' +}; + +describe('Optimon Analytics Adapter', () => { + const optmn_currentWindow = utils.getWindowSelf(); + let optmn_queue = []; + + beforeEach(() => { + optmn_currentWindow.OptimonAnalyticsAdapter = (...optmn_args) => optmn_queue.push(optmn_args); + adapterManager.enableAnalytics({ + provider: 'optimon' + }); + optmn_queue = [] + }); + + afterEach(() => { + optimonAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward all events to the queue', () => { + const optmn_arguments = [AD_UNIT_CODE, PUBLISHER_CONFIG]; + + events.emit(constants.EVENTS.AUCTION_END, optmn_arguments) + events.emit(constants.EVENTS.BID_TIMEOUT, optmn_arguments) + events.emit(constants.EVENTS.BID_WON, optmn_arguments) + + expect(optmn_queue.length).to.eql(3); + }); +}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js new file mode 100644 index 00000000000..4bdd657f419 --- /dev/null +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -0,0 +1,548 @@ +import {expect} from 'chai'; +import {spec} from 'modules/outbrainBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Outbrain Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed when bid contains native params', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should succeed when bid contains sizes', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'outbrain', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed with outbrain config', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + }) + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + request: JSON.stringify(expectedNativeAssets) + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + bidRequest.params.publisher.name = 'test-publisher' + bidRequest.params.publisher.domain = 'test-publisher.com' + bidRequest.params.bcat = ['bad-category'] + bidRequest.params.badv = ['bad-advertiser'] + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + expect(resData.site.publisher.name).to.equal('test-publisher') + expect(resData.site.publisher.domain).to.equal('test-publisher.com') + expect(resData.bcat).to.deep.equal(['bad-category']) + expect(resData.badv).to.deep.equal(['bad-advertiser']) + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.com' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250, + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig({ + outbrain: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled with outbrain config', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should pass GDPR consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` + }]); + }); + + it('should pass US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` + }]); + }); + + it('should pass GDPR and US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }]); + }); + }) + + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index d55bbc58056..cf1f3861eab 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -54,7 +54,7 @@ describe('permutiveRtdProvider', function () { }) } }) - it('sets segment targeting for Rubicon', function () { + it('sets segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -93,10 +93,29 @@ describe('permutiveRtdProvider', function () { }) } }) + it('sets segment targeting for TrustX', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) }) describe('Custom segment targeting', function () { - it('sets custom segment targeting for Rubicon', function () { + it('sets custom segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -129,6 +148,81 @@ describe('permutiveRtdProvider', function () { }) }) + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Magnite', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Ozone', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + describe('Permutive on page', function () { it('checks if Permutive is on page', function () { expect(isPermutiveOnPage()).to.equal(false) @@ -168,7 +262,7 @@ function getConfig () { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500 } } @@ -197,15 +291,20 @@ function getTargetingData () { } function getAdUnits () { + const div_1_sizes = [ + [300, 250], + [300, 600] + ] + const div_2_sizes = [ + [728, 90], + [970, 250] + ] return [ { code: '/19968336/header-bid-tag-0', mediaTypes: { banner: { - sizes: [ - [300, 250], - [300, 600] - ] + sizes: div_1_sizes } }, bids: [ @@ -214,7 +313,7 @@ function getAdUnits () { params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -228,7 +327,7 @@ function getAdUnits () { area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -242,12 +341,21 @@ function getAdUnits () { { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -255,17 +363,17 @@ function getAdUnits () { code: '/19968336/header-bid-tag-1', mediaTypes: { banner: { - sizes: [ - [728, 90], - [970, 250] - ] + sizes: div_2_sizes } }, bids: [ { bidder: 'appnexus', params: { - placementId: 13144370 + placementId: 13144370, + keywords: { + test_kv: ['true'] + } } }, { @@ -273,7 +381,14 @@ function getAdUnits () { params: { publisherId: 'OZONEGMG0001', siteId: '4204204209', - placementId: '0420420500' + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] } } ] diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index e5c6fe0324a..a6afed8ba3e 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -15,7 +15,10 @@ let CONFIG = { bidders: ['appnexus'], timeout: 1000, cacheMarkup: 2, - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; const REQUEST = { @@ -270,6 +273,7 @@ const RESPONSE_OPENRTB = { 'win': 'http://wurl.org?id=333' } }, + 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] }, 'bidder': { 'appnexus': { 'brand_id': 1, @@ -512,7 +516,7 @@ describe('S2S Adapter', function () { it('should default video placement if not defined and instream', function () { let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ s2sConfig: ortb2Config }); @@ -591,7 +595,7 @@ describe('S2S Adapter', function () { it('check gdpr info gets added into cookie_sync request: have consent data', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); @@ -617,7 +621,7 @@ describe('S2S Adapter', function () { it('check gdpr info gets added into cookie_sync request: have consent data but gdprApplies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); @@ -638,9 +642,9 @@ describe('S2S Adapter', function () { expect(requestBid.gdpr_consent).is.undefined; }); - it('checks gdpr info gets added to cookie_sync request: consent data unknown', function () { + it('checks gdpr info gets added to cookie_sync request: applies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); @@ -648,7 +652,7 @@ describe('S2S Adapter', function () { let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: undefined, - gdprApplies: undefined + gdprApplies: false }; const s2sBidRequest = utils.deepClone(REQUEST); @@ -657,7 +661,7 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.gdpr).is.undefined; + expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); }); @@ -689,7 +693,7 @@ describe('S2S Adapter', function () { it('is added to cookie_sync request when in bidRequest', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); let uspBidRequest = utils.deepClone(BID_REQUESTS); @@ -741,7 +745,7 @@ describe('S2S Adapter', function () { it('is added to cookie_sync request when in bidRequest', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); let consentBidRequest = utils.deepClone(BID_REQUESTS); @@ -788,7 +792,9 @@ describe('S2S Adapter', function () { it('adds device and app objects to request for OpenRTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); const _config = { @@ -1093,7 +1099,9 @@ describe('S2S Adapter', function () { it('skips pbs alias when skipPbsAliasing is enabled in adapter', function() { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); @@ -1122,7 +1130,9 @@ describe('S2S Adapter', function () { it('skips dynamic aliases to request when skipPbsAliasing enabled', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); @@ -1154,7 +1164,9 @@ describe('S2S Adapter', function () { it('converts appnexus params to expected format for PBS', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); @@ -1183,7 +1195,7 @@ describe('S2S Adapter', function () { it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; cookieSyncConfig.userSyncLimit = 1; const s2sBidRequest = utils.deepClone(REQUEST); @@ -1202,7 +1214,7 @@ describe('S2S Adapter', function () { it('does not add limit to cooke_sync request if userSyncLimit is missing or 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); const s2sBidRequest = utils.deepClone(REQUEST); @@ -1574,6 +1586,30 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); }); + it('passes multibid array in request', function () { + const bidRequests = utils.deepClone(BID_REQUESTS); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.multibid).to.deep.equal(expected); + }); + it('passes first party data in request', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1606,20 +1642,43 @@ describe('S2S Adapter', function () { const expected = allowedBidders.map(bidder => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } })); + const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); + expect(parsedRequestBody.user).to.deep.equal(commonUser); }); describe('pbAdSlot config', function () { - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1629,30 +1688,32 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1661,16 +1722,18 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } }; @@ -1679,13 +1742,13 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); - expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); }); }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1695,31 +1758,33 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adServer: { - adSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1729,18 +1794,20 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adserver: { - adSlot: '/a/b/c', - name: 'adserverName1' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } } } }; @@ -1750,14 +1817,40 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); + describe('ext.prebid config', function () { + it('should send \"imp.ext.prebid.storedrequest.id\" if \"ortb2Imp.ext.prebid.storedrequest.id\" is set', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + const storedRequestId = 'my-id'; + bidRequest.ad_units[0].ortb2Imp = { + ext: { + prebid: { + storedrequest: { + id: storedRequestId + } + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.prebid.storedrequest.id'); + expect(parsedRequestBody.imp[0].ext.prebid.storedrequest.id).to.equal(storedRequestId); + }); + }); + describe('response handler', function () { beforeEach(function () { sinon.stub(utils, 'triggerPixel'); @@ -1936,6 +2029,9 @@ describe('S2S Adapter', function () { expect(response).to.have.property('meta'); expect(response.meta).to.have.property('advertiserDomains'); expect(response.meta.advertiserDomains[0]).to.equal('appnexus.com'); + expect(response.meta).to.have.property('dchain'); + expect(response.meta.dchain.ver).to.equal('1.0'); + expect(response.meta.dchain.nodes[0].asi).to.equal('magnite.com'); expect(response).to.not.have.property('vastUrl'); expect(response).to.not.have.property('videoCacheKey'); expect(response).to.have.property('ttl', 60); @@ -1961,7 +2057,9 @@ describe('S2S Adapter', function () { it('handles OpenRTB video responses', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); @@ -1983,7 +2081,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.cache.vastXml', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2012,7 +2112,9 @@ describe('S2S Adapter', function () { it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2043,7 +2145,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2071,7 +2175,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting with wurl', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2098,7 +2204,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting with wurl and removes invalid targeting', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2133,7 +2241,9 @@ describe('S2S Adapter', function () { it('add request property pbsBidId with ext.prebid.bidid value', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -2157,7 +2267,9 @@ describe('S2S Adapter', function () { bidId: '123' }); const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); @@ -2201,7 +2313,9 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }) }); }); @@ -2290,7 +2404,9 @@ describe('S2S Adapter', function () { bidders: ['appnexus'], timeout: 1000, adapter: 'prebidServer', - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; config.setConfig({ s2sConfig: options }); @@ -2303,7 +2419,9 @@ describe('S2S Adapter', function () { enabled: true, timeout: 1000, adapter: 's2s', - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; config.setConfig({ s2sConfig: options }); @@ -2350,8 +2468,8 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid.adnxs.com/pbs/v1/cookie_sync'); + expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction'}); + expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync'}); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2371,8 +2489,8 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['rubicon']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', 'https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid-server.rubiconproject.com/cookie_sync'); + expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}); + expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2391,8 +2509,8 @@ describe('S2S Adapter', function () { 'bidders': ['rubicon'], 'defaultVendor': 'rubicon', 'enabled': true, - 'endpoint': 'https://prebid-server.rubiconproject.com/openrtb2/auction', - 'syncEndpoint': 'https://prebid-server.rubiconproject.com/cookie_sync', + 'endpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, + 'syncEndpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, 'timeout': 750 }) }); @@ -2413,8 +2531,8 @@ describe('S2S Adapter', function () { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', - endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', + endpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, + syncEndpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, }) }); @@ -2462,7 +2580,7 @@ describe('S2S Adapter', function () { // Add syncEndpoint so that the request goes to the User Sync endpoint // Modify the bidders property to include an alias for Rubicon adapter - s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; s2sConfig.bidders = ['appnexus', 'rubicon-alias']; const s2sBidRequest = utils.deepClone(REQUEST); @@ -2533,7 +2651,7 @@ describe('S2S Adapter', function () { it('should add cooperative sync flag to cookie_sync request if property is present', function () { let s2sConfig = utils.deepClone(CONFIG); s2sConfig.coopSync = false; - s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; const s2sBidRequest = utils.deepClone(REQUEST); s2sBidRequest.s2sConfig = s2sConfig; @@ -2548,7 +2666,7 @@ describe('S2S Adapter', function () { it('should not add cooperative sync flag to cookie_sync request if property is not present', function () { let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; const s2sBidRequest = utils.deepClone(REQUEST); s2sBidRequest.s2sConfig = s2sConfig; diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 410c3c59fb6..bdcdca06183 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,55 +1,62 @@ import { expect } from 'chai'; let { spec } = require('modules/proxistoreBidAdapter'); - const BIDDER_CODE = 'proxistore'; describe('ProxistoreBidAdapter', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const bidderRequest = { - 'bidderCode': BIDDER_CODE, - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', - 'bidderRequestId': '10edf38ec1a719', - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': 'CONSENT_STRING', - 'vendorData': { - 'vendorConsents': { - '418': true - } - } - } + bidderCode: BIDDER_CODE, + auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', + bidderRequestId: '10edf38ec1a719', + gdprConsent: { + apiVersion: 2, + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + 418: true, + }, + }, + }, + }, }; let bid = { sizes: [[300, 600]], params: { website: 'example.fr', - language: 'fr' + language: 'fr', + }, + ortb2: { + user: { ext: { data: { segments: [], contextual_categories: {} } } }, }, auctionId: 442133079, bidId: 464646969, - transactionId: 511916005 + transactionId: 511916005, }; describe('isBidRequestValid', function () { it('it should be true if required params are presents and there is no info in the local storage', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function() { + it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 1) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) - expect(spec.isBidRequestValid(bid)).to.equal(false); + date.setMinutes(date.getMinutes() - 1); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function() { + it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 10) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) + date.setMinutes(date.getMinutes() - 10); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); expect(spec.isBidRequestValid(bid)).to.equal(true); }); }); - describe('buildRequests', function () { - const url = 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi'; - const request = spec.buildRequests([bid], bidderRequest); + const url = { + cookieBase: 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi', + cookieLess: + 'https://abs.cookieless-proxistore.com/fr/v3/rtb/prebid/multi', + }; + let request = spec.buildRequests([bid], bidderRequest); it('should return a valid object', function () { expect(request).to.be.an('object'); expect(request.method).to.exist; @@ -59,16 +66,45 @@ describe('ProxistoreBidAdapter', function () { it('request method should be POST', function () { expect(request.method).to.equal('POST'); }); - it('should contain a valid url', function () { - expect(request.url).equal(url); - }); it('should have the value consentGiven to true bc we have 418 in the vendor list', function () { const data = JSON.parse(request.data); - - expect(data.gdpr.consentString).equal(bidderRequest.gdprConsent.consentString); + expect(data.gdpr.consentString).equal( + bidderRequest.gdprConsent.consentString + ); expect(data.gdpr.applies).to.be.true; expect(data.gdpr.consentGiven).to.be.true; }); + it('should contain a valid url', function () { + // has gdpr consent + expect(request.url).equal(url.cookieBase); + // doens't have gpdr consent + bidderRequest.gdprConsent.vendorData = null; + + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + + // api v2 + bidderRequest.gdprConsent = { + gdprApplies: true, + allowAuctionWithoutConsent: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + '418': true + } + }, + }, + apiVersion: 2 + }; + // has gdpr consent + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieBase); + + bidderRequest.gdprConsent.vendorData.vendor = {}; + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + }); it('should have a property a length of bids equal to one if there is only one bid', function () { const data = JSON.parse(request.data); expect(data.hasOwnProperty('bids')).to.be.true; @@ -77,51 +113,54 @@ describe('ProxistoreBidAdapter', function () { expect(data.bids[0].hasOwnProperty('id')).to.be.true; expect(data.bids[0].sizes).to.be.an('array'); }); - }); + it('should correctly set bidfloor on imp when getfloor in scope', function () { + let data = JSON.parse(request.data); + expect(data.bids[0].floor).to.be.null; + // make it respond with a non USD floor should not send it + bid.getFloor = function () { + return { currency: 'EUR', floor: 1.0 }; + }; + let req = spec.buildRequests([bid], bidderRequest); + data = JSON.parse(req.data); + expect(data.bids[0].floor).equal(1); + bid.getFloor = function () { + return { currency: 'USD', floor: 1.0 }; + }; + req = spec.buildRequests([bid], bidderRequest); + data = JSON.parse(req.data); + expect(data.bids[0].floor).to.be.null; + }); + }); describe('interpretResponse', function () { - const responses = { - body: - [{ + const emptyResponseParam = { body: [] }; + const fakeResponseParam = { + body: [ + { + ad: '', cpm: 6.25, - creativeId: '48fd47c9-ce35-4fda-804b-17e16c8c36ac', + creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', currency: 'EUR', - dealId: '2019-10_e3ecad8e-d07a-4c90-ad46-cd0f306c8960', + dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', height: 600, netRevenue: true, - requestId: '923756713', + requestId: '3543724f2a033c9', + segments: [], ttl: 10, vastUrl: null, vastXml: null, width: 300, - }] + }, + ], }; - const badResponse = { body: [] }; - const interpretedResponse = spec.interpretResponse(responses, bid)[0]; - it('should send an empty array if body is empty', function () { - expect(spec.interpretResponse(badResponse, bid)).to.be.an('array'); - expect(spec.interpretResponse(badResponse, bid).length).equal(0); - }); - it('should interpret the response correctly if it is valid', function () { - expect(interpretedResponse.cpm).equal(6.25); - expect(interpretedResponse.creativeId).equal('48fd47c9-ce35-4fda-804b-17e16c8c36ac'); - expect(interpretedResponse.currency).equal('EUR'); - expect(interpretedResponse.height).equal(600); - expect(interpretedResponse.width).equal(300); - expect(interpretedResponse.requestId).equal('923756713'); - expect(interpretedResponse.netRevenue).to.be.true; - expect(interpretedResponse.netRevenue).to.be.true; - }); - it('should have a value in the local storage if the response is empty', function() { - spec.interpretResponse(badResponse, bid); - expect(localStorage.getItem(`PX_NoAds_${bid.params.website}`)).to.be.string; - }); - }); - describe('interpretResponse', function () { - it('should aways return an empty array', function () { - expect(spec.getUserSyncs()).to.be.an('array'); - expect(spec.getUserSyncs().length).equal(0); + it('should always return an array', function () { + let response = spec.interpretResponse(emptyResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(0); + response = spec.interpretResponse(fakeResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(1); }); }); }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 382199dcffc..57b83fced06 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -225,8 +225,8 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest, bidRequest1], bidderRequest)).to.deep.equal(expectedRequest); }); - it('should take bid floor in bidder params', () => { - bidRequest.params.bidFloor = 0.5; + it('should take bid floor from getFloor interface', () => { + bidRequest.getFloor = () => ({ floor: 0.5, currency: 'USD' }); expectedRequest.data.imp[0].bidfloor = 0.5; expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 37deb0bca9c..28e79a69e55 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1348,6 +1348,214 @@ describe('PubMatic adapter', function () { expect(data2.regs).to.equal(undefined);// USP/CCPAs }); + describe('FPD', function() { + let newRequest; + + it('ortb2.site should be merged in the request', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); + sandbox.restore(); + }); + + it('ortb2.user should be merged in the request', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'ortb2': { + user: { + yob: 1985 + } + } + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.yob).to.equal(1985); + sandbox.restore(); + }); + + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should not send if imp[].ext.data.pbadslot is empty string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); + }); + + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + expect(data.imp[0].ext.dfp_ad_unit_code).to.equal(adSlotValue); + }); + }); + + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); + }); + }); + describe('setting imp.floor using floorModule', function() { /* Use the minimum value among floor from floorModule per mediaType @@ -1695,7 +1903,7 @@ describe('PubMatic adapter', function () { 'source': 'liveramp.com', 'uids': [{ 'id': 'identity-link-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1736,7 +1944,7 @@ describe('PubMatic adapter', function () { 'source': 'liveintent.com', 'uids': [{ 'id': 'live-intent-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1818,7 +2026,7 @@ describe('PubMatic adapter', function () { 'source': 'britepool.com', 'uids': [{ 'id': 'britepool-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -2614,7 +2822,7 @@ describe('PubMatic adapter', function () { } expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); + expect(response[0].netRevenue).to.equal(true); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); @@ -2638,7 +2846,7 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 95245f2c6c9..40f7afeeb6a 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import pubxaiAnalyticsAdapter from 'modules/pubxaiAnalyticsAdapter.js'; -import { getDeviceType } from 'modules/pubxaiAnalyticsAdapter.js'; +import { getDeviceType, getBrowser, getOS } from 'modules/pubxaiAnalyticsAdapter.js'; import { expect } from 'chai'; @@ -55,9 +55,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' } }], @@ -85,9 +85,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -151,9 +151,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -222,9 +222,9 @@ describe('pubxai analytics adapter', function() { 'originalCurrency': 'USD', 'floorData': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false, 'floorValue': 0.4, @@ -243,8 +243,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -265,8 +265,8 @@ describe('pubxai analytics adapter', function() { }, 'auctionEnd': { 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionEnd': 1603865707180, + 'timestamp': 1616654312804, + 'auctionEnd': 1616654313090, 'auctionStatus': 'completed', 'adUnits': [{ 'code': '/19968336/header-bid-tag-1', @@ -289,9 +289,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' } }], @@ -319,9 +319,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -390,9 +390,9 @@ describe('pubxai analytics adapter', function() { 'originalCurrency': 'USD', 'floorData': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false, 'floorValue': 0.4, @@ -411,8 +411,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -464,9 +464,9 @@ describe('pubxai analytics adapter', function() { 'originalCurrency': 'USD', 'floorData': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false, 'floorValue': 0.4, @@ -485,8 +485,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -521,11 +521,10 @@ describe('pubxai analytics adapter', function() { 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', - 'requestId': '184cbc05bb90ba', 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'sizes': '300x250', 'renderStatus': 2, - 'requestTimestamp': 1603865707182, + 'requestTimestamp': 1616654312804, 'creativeId': 96846035, 'currency': 'USD', 'cpm': 0.5, @@ -534,9 +533,9 @@ describe('pubxai analytics adapter', function() { 'statusMessage': 'Bid available', 'floorData': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false, 'floorValue': 0.4, @@ -555,48 +554,45 @@ describe('pubxai analytics adapter', function() { } }, 'timeToRespond': 267, - 'responseTimestamp': 1603865707449, - 'platform': navigator.platform, - 'placementId': 13144370, - 'deviceType': getDeviceType() + 'responseTimestamp': 1616654313071 }], 'pageDetail': { 'host': location.host, 'path': location.pathname, - 'search': location.search + 'search': location.search, + 'adUnitCount': 1 }, 'floorDetail': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false }, + 'deviceDetail': { + 'platform': navigator.platform, + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() + }, 'initOptions': initOptions }; let expectedAfterBidWon = { 'winningBid': { - 'bidderCode': 'appnexus', - 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'renderedSize': '300x250', - 'renderStatus': 4, - 'requestTimestamp': 1603865707182, + 'bidderCode': 'appnexus', + 'bidId': '248f9a4489835e', + 'cpm': 0.5, 'creativeId': 96846035, 'currency': 'USD', - 'cpm': 0.5, - 'netRevenue': true, - 'mediaType': 'banner', - 'status': 'rendered', - 'statusMessage': 'Bid available', 'floorData': { 'fetchStatus': 'success', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'location': 'fetch', - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'skipRate': 0, 'skipped': false, 'floorValue': 0.4, @@ -614,15 +610,24 @@ describe('pubxai analytics adapter', function() { 'mediaType': 'banner' } }, - 'timeToRespond': 267, - 'responseTimestamp': 1603865707449, - 'platform': navigator.platform, - 'deviceType': getDeviceType() + 'floorProvider': 'PubXFloorProvider', + 'isWinningBid': true, + 'mediaType': 'banner', + 'netRevenue': true, + 'placementId': 13144370, + 'renderedSize': '300x250', + 'renderStatus': 4, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'status': 'rendered', + 'statusMessage': 'Bid available', + 'timeToRespond': 267 }, - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search + 'deviceDetail': { + 'platform': navigator.platform, + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() }, 'initOptions': initOptions } diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 91e3cf4bfdf..5d56203131a 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -70,12 +70,23 @@ describe('qwarryBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + const bidderRequest = spec.buildRequests(bidRequests, { + bidderRequestId: '123', + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + }, + refererInfo: { + referer: 'http://test.com/path.html' + } + }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) + expect(bidderRequest.data.gdprConsent).to.deep.contains({ consentRequired: true, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', gdpr: 1 }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index c629daf3da5..c3c3b4b2746 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -87,12 +87,25 @@ describe('radsAdapter', function () { 'auctionId': '1d1a030790a475' }]; + // Without gdprConsent let bidderRequest = { refererInfo: { referer: 'some_referrer.net' } } + // With gdprConsent + var bidderRequestGdprConsent = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + // without gdprConsent const request = spec.buildRequests(bidRequests, bidderRequest); it('sends bid request to our endpoint via GET', function () { expect(request[0].method).to.equal('GET'); @@ -105,6 +118,20 @@ describe('radsAdapter', function () { let data = request[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&bcat=IAB2%2CIAB4&dvt=desktop'); }); + + // with gdprConsent + const request2 = spec.buildRequests(bidRequests, bidderRequestGdprConsent); + it('sends bid request to our endpoint via GET', function () { + expect(request2[0].method).to.equal('GET'); + let data = request2[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('rt=bid-response&_f=prebid_js&_ps=6682&srw=300&srh=250&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1'); + }); + + it('sends bid video request to our rads endpoint via GET', function () { + expect(request2[1].method).to.equal('GET'); + let data = request2[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop'); + }); }); describe('interpretResponse', function () { @@ -203,4 +230,60 @@ describe('radsAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); }); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index d5a877f6221..eefd7792a7c 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -189,6 +189,73 @@ describe('ReadPeakAdapter', function() { language: navigator.language }); expect(data.cur).to.deep.equal(['EUR']); + expect(data.user).to.be.undefined; + expect(data.regs).to.be.undefined; + }); + + it('should get bid floor from module', function() { + const floorModuleData = { + currency: 'USD', + floor: 3.2, + } + bidRequest.getFloor = function () { + return floorModuleData + } + const request = spec.buildRequests([bidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it('should send gdpr data when gdpr does not apply', function() { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: '' + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false + } + }); + }); + + it('should send gdpr data when gdpr applies', function() { + const tcString = 'sometcstring'; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true + } + }); }); }); @@ -213,6 +280,9 @@ describe('ReadPeakAdapter', function() { currency: serverResponse.cur }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ['readpeak.com'], + }) expect(bidResponse.native.title).to.equal('Title'); expect(bidResponse.native.body).to.equal('Description'); expect(bidResponse.native.image).to.deep.equal({ diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index d9342332e61..93735c019e1 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -157,6 +157,7 @@ describe('rhythmone adapter tests', function () { expect(bid.width).to.equal(800); expect(bid.height).to.equal(600); expect(bid.vastUrl).to.equal('https://testdomain/rmp/placementid/0/path?reqId=1636037'); + expect(bid.meta.advertiserDomains).to.deep.equal(['test.com']); expect(bid.mediaType).to.equal('video'); expect(bid.creativeId).to.equal('cr-vid'); expect(bid.currency).to.equal('USD'); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 3b9f07b6efc..72410b71fb2 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.5, pid: 'ADb1f40rmi', supplyType: 'site', - keywords: 'coche=mercedes;coche=audi' + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -242,7 +235,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') - expect(requestContent).to.have.property('kws').and.to.equal('coche=mercedes;coche=audi'); + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -262,8 +255,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -281,6 +272,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -623,9 +615,19 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); }); it('no banner media response outstream', function () { @@ -640,6 +642,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -656,6 +687,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -717,78 +758,373 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); - it('Verifies user syncs iframe', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + describe('userSync', function () { + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); - expect(syncs).to.have.lengthOf(0); - syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - config.setConfig({ - consentManagement: { - cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true, + syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - } + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); - }); - it('Verifies user syncs image', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + }) }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 176437c4f27..b3257cbda9d 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('riseAdapter', function () { bidderCode: 'rise', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -83,6 +85,22 @@ describe('riseAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('sends bid request to test ENDPOINT via GET', function () { const requests = spec.buildRequests(testModeBidRequests, bidderRequest); for (const request of requests) { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index efaed87dd15..77dfff35c4a 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -424,6 +424,7 @@ describe('RTBHouseAdapter', () => { 'mediaType': 'banner', 'currency': 'USD', 'ttl': 300, + 'meta': { advertiserDomains: ['rtbhouse.com'] }, 'netRevenue': true } ]; @@ -486,6 +487,7 @@ describe('RTBHouseAdapter', () => { it('should contain native assets in valid format', () => { const bids = spec.interpretResponse({body: response}, {}); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['rtbhouse.com']); expect(bids[0].native).to.deep.equal({ title: 'Title text', clickUrl: encodeURIComponent('https://example.com'), diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 136f5ef240a..df8bf03b56a 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -135,6 +135,31 @@ const BID3 = Object.assign({}, BID, { } }); +const BID4 = Object.assign({}, BID, { + adUnitCode: '/19968336/header-bid-tag1', + bidId: '3bd4ebb1c900e2', + adId: 'fake_ad_id', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + source: 'server', + pbsBidId: 'zzzz-yyyy-xxxx-wwww', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/header-bid-tag1', + 'rpfl_14062': '2_tier0100' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + } +}); + const floorMinRequest = { 'bidder': 'rubicon', 'params': { @@ -349,6 +374,7 @@ const ANALYTICS_MESSAGE = { 'referrerHostname': 'www.test.com', 'auctions': [ { + 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'clientTimeoutMillis': 3000, 'serverTimeoutMillis': 1000, 'accountId': 1001, @@ -1436,8 +1462,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot0, isEmpty: false, advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 } }; @@ -1448,8 +1474,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666 + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 } }; }); @@ -1515,8 +1541,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 0, - creativeId: 0, - lineItemId: 0 + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 } }]); expect(server.requests.length).to.equal(1); @@ -1540,6 +1566,38 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + it('should correctly set adUnit for associated slots', function () { performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); expect(server.requests.length).to.equal(1); @@ -1683,6 +1741,73 @@ describe('rubicon analytics adapter', function () { expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); }); + it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + }); + + it('should correctly generate new bidId if it is 0', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + seatBidResponse.pbsBidId = '0'; + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); + expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); + }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { // Only want one bid request in our mock auction let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); @@ -1782,14 +1907,18 @@ describe('rubicon analytics adapter', function () { it('should pass aupName as pattern', function () { let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].fpd = { - context: { - aupName: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' + bidRequest.bids[0].ortb2Imp = { + ext: { + data: { + aupname: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' + } } }; - bidRequest.bids[1].fpd = { - context: { - aupName: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' + bidRequest.bids[1].ortb2Imp = { + ext: { + data: { + aupname: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' + } } }; events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); @@ -1809,6 +1938,32 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); }); + it('should pass bidderDetail for multibid auctions', function () { + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse.targetingBidder = 'rubi2'; + bidResponse.originalRequestId = bidResponse.requestId; + bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + + expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); + expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); + }); + it('should successfully convert bid price to USD in parseBidResponse', function () { // Set the rates setConfig({ diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 8c25d97dada..6b061e3157d 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -371,6 +371,7 @@ describe('the rubicon adapter', function () { utils.logError.restore(); config.resetConfig(); resetRubiConf(); + delete $$PREBID_GLOBAL$$.installedModules; }); describe('MAS mapping / ordering', function () { @@ -488,6 +489,17 @@ describe('the rubicon adapter', function () { data = parseQuery(request.data); expect(data.rp_hard_floor).to.equal('1.23'); }); + + it('should send rp_maxbids to AE if rubicon multibid config exists', function () { + var multibidRequest = utils.deepClone(bidderRequest); + multibidRequest.bidLimit = 5; + + let [request] = spec.buildRequests(multibidRequest.bids, multibidRequest); + let data = parseQuery(request.data); + + expect(data['rp_maxbids']).to.equal('5'); + }); + it('should not send p_pos to AE if not params.position specified', function () { var noposRequest = utils.deepClone(bidderRequest); delete noposRequest.bids[0].params.position; @@ -554,7 +566,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -827,27 +839,39 @@ describe('the rubicon adapter', function () { }); it('should merge first party data from getConfig with the bid params, if present', () => { - const context = { + const site = { keywords: 'e,f', rating: '4-star', - data: { - page: 'home' + ext: { + data: { + page: 'home' + } } }; const user = { + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'taxonomyname': 'IAB Audience Taxonomy' }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', geo: {country: 'ca'}, keywords: 'd', - data: { - age: 40 + ext: { + data: { + age: 40 + } } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -861,8 +885,9 @@ describe('the rubicon adapter', function () { 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', 'tg_v.age': '40', + 'tg_v.iab': '687,123', 'tg_v.yob': '1984', - 'tg_i.rating': '5-star', + 'tg_i.rating': '4-star,5-star', 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1287,12 +1312,12 @@ describe('the rubicon adapter', function () { describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1300,8 +1325,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1310,10 +1335,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1324,10 +1351,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } } } @@ -1339,12 +1368,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } - }; + } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1358,12 +1389,12 @@ describe('the rubicon adapter', function () { describe('GAM ad unit', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1371,8 +1402,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1381,11 +1412,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '' + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1397,11 +1430,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } } } } @@ -1414,11 +1449,13 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'a/b/c' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } } } }; @@ -1473,7 +1510,7 @@ describe('the rubicon adapter', function () { // LiveIntent should exist expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.eids[0].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); @@ -1481,7 +1518,7 @@ describe('the rubicon adapter', function () { // LiveRamp should exist expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); - expect(post.user.ext.eids[1].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); // SharedId should exist expect(post.user.ext.eids[2].source).to.equal('sharedid.org'); expect(post.user.ext.eids[2].uids[0].id).to.equal('1111'); @@ -1601,6 +1638,62 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); + + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); + + it('should pass client analytics to PBS endpoint if all modules included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal([{'adapter': 'rubicon', 'client-analytics': true}]); + }); + + it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal([{'adapter': 'rubicon', 'client-analytics': true}]); + }); + + it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.be.undefined; + }); + it('should send video exp param correctly when set', function () { createVideoBidderRequest(); config.setConfig({s2sConfig: {defaultTtl: 600}}); @@ -1758,16 +1851,6 @@ describe('the rubicon adapter', function () { delete bidderRequest.bids[0].mediaTypes.video.protocols; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change maxduration to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.maxduration = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete maxduration, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.maxduration; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change linearity to an string, no good createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; @@ -1871,27 +1954,36 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - data: { - page: 'home' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] }, keywords: 'e,f', - rating: '4-star' + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - data: { - age: 31 + ext: { + data: { + age: 31 + } }, keywords: 'd', gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -1901,19 +1993,19 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const expected = { - site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), - user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), }; - delete expected.site.data; - delete expected.user.data; - delete expected.site.keywords; - delete expected.user.keywords; + delete request.data.site.page; + delete request.data.site.content.language; expect(request.data.site.keywords).to.deep.equal('a,b,c'); expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.site); - expect(request.data.user.ext.data).to.deep.equal(expected.user); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1932,29 +2024,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '1234567890', - name: 'adServerName1' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } }; @@ -1964,8 +2060,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { @@ -2518,6 +2614,146 @@ describe('the rubicon adapter', function () { expect(bids[0].cpm).to.be.equal(0); }); + it('should create bids with matching requestIds if imp id matches', function () { + let bidRequests = [{ + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '557ba307cef098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }, { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '456gt123jkl098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }]; + + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 1.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + + config.setConfig({ multibid: [{bidder: 'rubicon', maxbids: 2, targetbiddercodeprefix: 'rubi'}] }); + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidRequests + }); + + expect(bids).to.be.lengthOf(3); + expect(bids[0].requestId).to.not.equal(bids[1].requestId); + expect(bids[1].requestId).to.equal(bids[2].requestId); + }); + it('should handle an error with no ads returned', function () { let response = { 'status': 'ok', @@ -2814,6 +3050,153 @@ describe('the rubicon adapter', function () { }); }); + describe('for outstream video', function () { + const sandbox = sinon.createSandbox(); + beforeEach(function () { + createVideoBidderRequestOutstream(); + config.setConfig({rubicon: { + rendererConfig: { + align: 'left', + closeButton: true + }, + rendererUrl: 'https://example.test/renderer.js' + }}); + window.MagniteApex = { + renderAd: function() { + return null; + } + } + }); + + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); + + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: 'outstream_video1', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + } + }], + group: 0, + seat: 'rubicon' + }], + }; + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(320); + // check custom renderer + expect(typeof bids[0].renderer).to.equal('object'); + expect(bids[0].renderer.getConfig()).to.deep.equal({ + align: 'left', + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: 'outstream_video1', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + }, + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' + }], + }; + + sinon.spy(window.MagniteApex, 'renderAd'); + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 320, + label: undefined, + placement: { + align: 'left', + attachTo: '#outstream_video1_placement', + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.remove(); + }); + }); + describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { config.setConfig({rubicon: {int_type: 'testType'}}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 8957bde6bd9..c82c8300d2e 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai' import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' +import * as utils from 'src/utils.js' const PUBLISHER_ID = '0000-0000-01' const ADUNIT_ID = '000000' @@ -42,7 +43,7 @@ describe('Seedtag Adapter', function() { } ) } - const placements = ['banner', 'video', 'inImage', 'inScreen'] + const placements = ['banner', 'video', 'inImage', 'inScreen', 'inArticle'] placements.forEach(placement => { it('should be ' + placement, function() { const isBidRequestValid = spec.isBidRequestValid( @@ -53,7 +54,7 @@ describe('Seedtag Adapter', function() { }) }) }) - describe('when video slot has all mandatory params.', function() { + describe('when video slot has all mandatory params', function() { it('should return true, when video mediatype object are correct.', function() { const slotConfig = getSlotConfigs( { @@ -116,7 +117,7 @@ describe('Seedtag Adapter', function() { expect(isBidRequestValid).to.equal(false) }) }) - describe('when video mediaType object is not correct.', function() { + describe('when video mediaType object is not correct', function() { function createVideoSlotconfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, @@ -199,6 +200,7 @@ describe('Seedtag Adapter', function() { expect(data.url).to.equal('referer') expect(data.publisherToken).to.equal('0000-0000-01') expect(typeof data.version).to.equal('string') + expect(['fixed', 'mobile', 'unknown'].indexOf(data.connectionType)).to.be.above(-1) }) describe('adPosition param', function() { @@ -301,7 +303,7 @@ describe('Seedtag Adapter', function() { expect(typeof bids).to.equal('object') expect(bids.length).to.equal(0) }) - it('should return a void array, when the server response have not got bids.', function() { + it('should return a void array, when the server response have no bids.', function() { const request = { data: JSON.stringify({}) } const serverResponse = { body: { bids: [] } } const bids = spec.interpretResponse(serverResponse, request) @@ -323,7 +325,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'display', - ttl: 360 + ttl: 360, + nurl: 'testurl.com/nurl' } ], cookieSync: { url: '' } @@ -338,6 +341,7 @@ describe('Seedtag Adapter', function() { expect(bids[0].currency).to.equal('USD') expect(bids[0].netRevenue).to.equal(true) expect(bids[0].ad).to.equal('content') + expect(bids[0].nurl).to.equal('testurl.com/nurl') }) }) describe('the bid is a video', function() { @@ -354,7 +358,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'video', - ttl: 360 + ttl: 360, + nurl: undefined } ], cookieSync: { url: '' } @@ -404,6 +409,14 @@ describe('Seedtag Adapter', function() { }) describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + it('should return the correct endpoint', function () { const params = { publisherId: '0000', adUnitId: '11111' } const timeoutData = [{ params: [ params ] }]; @@ -415,5 +428,44 @@ describe('Seedtag Adapter', function() { params.adUnitId ) }) + + it('should set the timeout pixel', function() { + const params = { publisherId: '0000', adUnitId: '11111' } + const timeoutData = [{ params: [ params ] }]; + spec.onTimeout(timeoutData) + expect(utils.triggerPixel.calledWith('https://s.seedtag.com/se/hb/timeout?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId)).to.equal(true); + }) + }) + + describe('onBidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + + describe('without nurl', function() { + const bid = {} + + it('does not create pixel ', function() { + spec.onBidWon(bid) + expect(utils.triggerPixel.called).to.equal(false); + }) + }) + + describe('with nurl', function () { + const nurl = 'http://seedtag_domain/won' + const bid = { nurl } + + it('creates nurl pixel if bid nurl', function() { + spec.onBidWon({ nurl }) + expect(utils.triggerPixel.calledWith(nurl)).to.equal(true); + }) + }) }) }) diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index b3451a09dde..57306580ecc 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from '../../../src/utils.js'; +import { config } from 'src/config'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -441,6 +442,29 @@ describe('sharethrough adapter spec', function() { const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data).to.not.include.any.keys('bidfloor'); }); + + describe('coppa', function() { + it('should add coppa to request if enabled', function() { + config.setConfig({coppa: true}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.eq(true); + }); + + it('should not add coppa to request if disabled', function() { + config.setConfig({coppa: false}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + + it('should not add coppa to request if unknown value', function() { + config.setConfig({coppa: 'something'}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + }); }); describe('.interpretResponse', function() { diff --git a/test/spec/modules/shinezBidAdapter_spec.js b/test/spec/modules/shinezBidAdapter_spec.js new file mode 100644 index 00000000000..cc3c2451c5d --- /dev/null +++ b/test/spec/modules/shinezBidAdapter_spec.js @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, internal } from 'modules/shinezBidAdapter.js'; + +describe('shinezBidAdapter', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }); + describe('isBidRequestValid', () => { + const cases = [ + [ + 'should return false when placementId is missing', + { + params: {}, + }, + false, + ], + [ + 'should return false when placementId has wrong type', + { + params: { + placementId: 123, + }, + }, + false, + ], + [ + 'should return false when unit has wrong type', + { + params: { + placementId: '00654321', + unit: 150, + }, + }, + false, + ], + [ + 'should return true when required params found and valid', + { + params: { + placementId: '00654321', + }, + }, + true, + ], + [ + 'should return true when all params found and valid', + { + params: { + placementId: '00654321', + unit: '__header-bid-1', + }, + }, + true, + ], + ]; + cases.map(([description, request, expected]) => { + it(description, () => { + const result = spec.isBidRequestValid(request); + expect(result).to.be.equal(expected); + }); + }); + }); + describe('buildRequests', () => { + it('should build server request correctly', () => { + const utcOffset = 300; + const validBidRequests = [ + { + params: { + placementId: '00654321', + unit: 'header-bid-tag-1-shinez', + }, + crumbs: { + pubcid: 'c8584a82-bec3-4347-8d3e-e7612438a161', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + adUnitCode: 'header-bid-tag-1', + transactionId: '665760dc-a249-4be7-ae86-91f417b2c65d', + }, + ]; + const bidderRequest = { + refererInfo: { + referer: 'http://site-with-ads.com', + }, + }; + sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(utcOffset); + const result = spec.buildRequests(validBidRequests, bidderRequest); + const expectedData = [ + { + bidId: validBidRequests[0].bidId, + transactionId: validBidRequests[0].transactionId, + crumbs: validBidRequests[0].crumbs, + mediaTypes: validBidRequests[0].mediaTypes, + refererInfo: bidderRequest.refererInfo, + adUnitCode: validBidRequests[0].adUnitCode, + utcOffset: utcOffset, + placementId: validBidRequests[0].params.placementId, + unit: validBidRequests[0].params.unit, + }, + ]; + expect(result.method, "request should be POST'ed").equal('POST'); + expect(result.url, 'request should be send to correct url').equal( + internal.TARGET_URL + ); + expect(result.data, 'request should have correct payload').to.deep.equal( + expectedData + ); + }); + }); + describe('interpretResponse', () => { + it('should interpret bid responses correctly', () => { + const response = { + body: [ + { + bidId: '2ece6496f4d0c9', + cpm: 0.03, + currency: 'USD', + width: 300, + height: 250, + ad: `

The Ad

`, + ttl: 60, + creativeId: 'V8qlA6guwm', + netRevenue: true, + }, + ], + }; + const bids = [ + { + requestId: response.body[0].bidId, + cpm: response.body[0].cpm, + currency: response.body[0].currency, + width: response.body[0].width, + height: response.body[0].height, + ad: response.body[0].ad, + ttl: response.body[0].ttl, + creativeId: response.body[0].creativeId, + netRevenue: response.body[0].netRevenue, + }, + ]; + const result = spec.interpretResponse(response); + expect(result).to.deep.equal(bids); + }); + }); +}); diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js new file mode 100644 index 00000000000..a16359c50cb --- /dev/null +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -0,0 +1,94 @@ +import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('sirdataRtdProvider', function() { + describe('sirdataSubmodule', function() { + it('successfully instantiates', function () { + expect(sirdataSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Segment Data', function() { + it('adds segment data', function() { + const config = { + params: { + setGptKeyValues: false, + contextualMinRelevancyScore: 50, + bidders: [{ + bidder: 'appnexus' + }, { + bidder: 'other' + }] + } + }; + + let adUnits = [ + { + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + } + ]; + + let data = { + segments: [111111, 222222], + contextual_categories: {'333333': 100} + }; + + addSegmentData(adUnits, data, config, () => {}); + expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); + expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); + expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + }); + }); + + describe('Get Segments And Categories', function() { + it('gets data from async request and adds segment data', function() { + const config = { + params: { + setGptKeyValues: false, + contextualMinRelevancyScore: 50, + bidders: [{ + bidder: 'appnexus' + }, { + bidder: 'other' + }] + } + }; + + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + }] + }; + + let data = { + segments: [111111, 222222], + contextual_categories: {'333333': 100} + }; + + getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {}); + + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + }); + }); +}); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 6af0a855800..1bc77fc9572 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -42,7 +42,7 @@ const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const context = { +const site = { keywords: 'power tools,drills' }; @@ -439,8 +439,8 @@ describe('smaatoBidAdapterTest', () => { it('sends fp data', () => { this.sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index cb5ceee0870..a7f30bdec6e 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -69,9 +69,6 @@ describe('SmartRTBBidAdapter', 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 () { @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + it('should return false if any zone id and pub id missing', function () { + expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 82c6642bd74..3c871c6f88b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -421,6 +421,9 @@ describe('The smartx adapter', function () { it('should return an array of bid responses', function () { var responses = spec.interpretResponse(serverResponse, bidderRequestObj); expect(responses).to.be.an('array').with.length(2); + expect(bidderRequestObj).to.be.an('Object'); + expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -430,6 +433,7 @@ describe('The smartx adapter', function () { expect(responses[0].mediaType).to.equal('video'); expect(responses[0].width).to.equal(400); expect(responses[0].height).to.equal(300); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].requestId).to.equal(124); expect(responses[1].currency).to.equal('USD'); expect(responses[1].cpm).to.equal(13); @@ -505,7 +509,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777777'); + expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); window.document.getElementById.restore(); }); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 2780e88255d..8804050134a 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from '../../../modules/smartyadsBidAdapter.js'; +import { config } from '../../../src/config.js'; describe('SmartyadsAdapter', function () { let bid = { @@ -38,9 +39,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); @@ -57,6 +59,23 @@ describe('SmartyadsAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); + }); + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 0ac242ce0e1..d0d8b65a42f 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -305,12 +305,12 @@ describe('smilewantedBidAdapterTests', function () { }); it('SmileWanted - Verify user sync', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE_DISPLAY]); + var syncs = spec.getUserSyncs({iframeEnabled: true}, {}, { + consentString: 'foo' + }, '1NYN'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com?gdpr_consent=foo&us_privacy=1NYN'); syncs = spec.getUserSyncs({ iframeEnabled: false @@ -320,6 +320,6 @@ describe('smilewantedBidAdapterTests', function () { syncs = spec.getUserSyncs({ iframeEnabled: true }, []); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.have.lengthOf(1); }); }); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52821072a21..0314ffb71c1 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -2,10 +2,10 @@ import { expect } from 'chai' import { spec, _getPlatform } from 'modules/sonobiBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import {userSync} from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('SonobiBidAdapter', function () { const adapter = newBidder(spec) - describe('.code', function () { it('should return a bidder code of sonobi', function () { expect(spec.code).to.equal('sonobi') @@ -304,6 +304,20 @@ describe('SonobiBidAdapter', function () { uspConsent: 'someCCPAString' }; + it('should populate coppa as 1 if set in config', function () { + config.setConfig({coppa: true}); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + + expect(bidRequests.data.coppa).to.equal(1); + }); + + it('should populate coppa as 0 if set in config', function () { + config.setConfig({coppa: false}); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + + expect(bidRequests.data.coppa).to.equal(0); + }); + it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) const bidRequestsPageViewID = spec.buildRequests(bidRequest, bidderRequests) @@ -414,15 +428,92 @@ describe('SonobiBidAdapter', function () { expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { + bidRequest[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + bidRequest[1].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(JSON.parse(bidRequests.data.eids)).to.eql([ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]); + }); + it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; + bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; + bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}); + expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ'}); }); it('should return a properly formatted request with userid omitted if there are no userIds', function () { @@ -469,7 +560,7 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bRequest, bidderRequests); expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); - }) + }); }); describe('.interpretResponse', function () { diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js index 8d2b4eea87d..97f80e8dfeb 100644 --- a/test/spec/modules/sortableBidAdapter_spec.js +++ b/test/spec/modules/sortableBidAdapter_spec.js @@ -329,7 +329,40 @@ describe('sortableBidAdapter', function() { const gdprRequest = spec.buildRequests(gdprBidRequests, {refererInfo: { referer: 'http://localhost:9876/' }}); const gdprRequestBody = JSON.parse(gdprRequest.data); expect(gdprRequestBody.regs).to.deep.equal({ext: {}}); - expect(gdprRequestBody.user).to.equal(undefined); + expect(gdprRequestBody.user.ext.consent).to.equal(undefined); + }) + + const eidsBidRequests = [{ + 'bidder': 'sortable', + 'params': { + 'tagId': '403370', + 'siteId': 'example.com', + 'floor': 0.21, + 'keywords': {}, + 'floorSizeMap': {} + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + it('should not set user ids when none present', function() { + const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { + referer: 'http://localhost:9876/' + }}); + const eidsRequestBody = JSON.parse(eidsRequest.data); + + expect(eidsRequestBody.user.ext.eids).to.equal(undefined); + }) + + it('should set user ids when present', function() { + eidsBidRequests[0].userId = { criteoId: 'sample-userid' }; + const eidsRequest = spec.buildRequests(eidsBidRequests, {refererInfo: { + referer: 'http://localhost:9876/' + }}); + const eidsRequestBody = JSON.parse(eidsRequest.data); + + expect(eidsRequestBody.user.ext.eids.length).to.equal(1); }) }); diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 84a0069c0b4..1050fdf4856 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,13 +1,14 @@ import {expect} from 'chai'; -import {LogError, spec} from 'modules/sovrnBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; +import {spec} from 'modules/sovrnBidAdapter.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js' const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; const adUnitBidRequest = { 'bidder': 'sovrn', 'params': { - 'tagid': '403370' + 'tagid': 403370 }, 'adUnitCode': 'adunit-code', 'sizes': [ @@ -18,82 +19,61 @@ const adUnitBidRequest = { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', } +const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } +}; describe('sovrnBidAdapter', function() { - const adapter = newBidder(spec); - describe('isBidRequestValid', function () { - const bid = { - 'bidder': 'sovrn', - 'params': { - 'tagid': '403370' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(adUnitBidRequest)).to.equal(true); }); it('should return false when tagid not passed correctly', function () { - bid.params.tagid = 'ABCD'; - expect(spec.isBidRequestValid(bid)).to.equal(false); + const bid = {...adUnitBidRequest} + const params = adUnitBidRequest.params + bid.params = {...params} + bid.params.tagid = 'ABCD' + expect(spec.isBidRequestValid(bid)).to.equal(false) }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); + const bid = {...adUnitBidRequest} bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); describe('buildRequests', function () { - const bidRequests = [{ - 'bidder': 'sovrn', - 'params': { - 'tagid': '403370' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - it('attaches source and version to endpoint URL as query params', function () { - expect(request.url).to.equal(ENDPOINT) - }); - - it('sets the proper banner object', function() { - const payload = JSON.parse(request.data) - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) - expect(payload.imp[0].banner.w).to.equal(1) - expect(payload.imp[0].banner.h).to.equal(1) - }) - - it('includes the ad unit code int the request', function() { - const payload = JSON.parse(request.data); - expect(payload.imp[0].adunitcode).to.equal('adunit-code') + describe('basic bid parameters', function() { + const bidRequests = [adUnitBidRequest]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request.url).to.equal(ENDPOINT) + }); + + it('sets the proper banner object', function() { + const payload = JSON.parse(request.data) + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + + it('includes the ad unit code int the request', function() { + const payload = JSON.parse(request.data); + expect(payload.imp[0].adunitcode).to.equal('adunit-code') + }) + + it('converts tagid to string', function () { + expect(request.data).to.contain('"tagid":"403370"') + }); }) it('accepts a single array as a size', function() { @@ -109,11 +89,6 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' }] - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } - } const request = spec.buildRequests(singleSize, bidderRequest) const payload = JSON.parse(request.data) expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]) @@ -149,7 +124,7 @@ describe('sovrnBidAdapter', function() { it('sends gdpr info if exists', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { + const bidderRequest = { 'bidderCode': 'sovrn', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', @@ -162,9 +137,9 @@ describe('sovrnBidAdapter', function() { referer: 'http://example.com/page.html', } }; - bidderRequest.bids = bidRequests; + bidderRequest.bids = [adUnitBidRequest]; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + const data = JSON.parse(spec.buildRequests([adUnitBidRequest], bidderRequest).data); expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); expect(data.regs.ext.gdpr).to.equal(1); @@ -173,8 +148,8 @@ describe('sovrnBidAdapter', function() { }); it('should send us_privacy if bidderRequest has a value for uspConsent', function () { - let uspString = '1NYN'; - let bidderRequest = { + const uspString = '1NYN'; + const bidderRequest = { 'bidderCode': 'sovrn', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', @@ -184,39 +159,13 @@ describe('sovrnBidAdapter', function() { referer: 'http://example.com/page.html', } }; - bidderRequest.bids = bidRequests; + bidderRequest.bids = [adUnitBidRequest]; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + const data = JSON.parse(spec.buildRequests([adUnitBidRequest], bidderRequest).data); expect(data.regs.ext['us_privacy']).to.equal(uspString); }); - it('converts tagid to string', function () { - const ivBidRequests = [{ - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370, - 'iv': 'vet' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } - }; - const request = spec.buildRequests(ivBidRequests, bidderRequest); - - expect(request.data).to.contain('"tagid":"403370"') - }); - it('should add schain if present', function() { const schainRequests = [{ 'bidder': 'sovrn', @@ -243,7 +192,7 @@ describe('sovrnBidAdapter', function() { } ] } - }].concat(bidRequests); + }].concat(adUnitBidRequest); const bidderRequest = { refererInfo: { referer: 'http://example.com/page.html', @@ -272,7 +221,7 @@ describe('sovrnBidAdapter', function() { 'criteoId': 'A_CRITEO_ID', 'tdid': 'SOMESORTOFID', } - }].concat(bidRequests); + }].concat(adUnitBidRequest); const bidderRequest = { refererInfo: { referer: 'http://example.com/page.html', @@ -293,6 +242,7 @@ describe('sovrnBidAdapter', function() { }); it('should ignore empty segments', function() { + const request = spec.buildRequests([adUnitBidRequest], bidderRequest) const payload = JSON.parse(request.data) expect(payload.imp[0].ext).to.be.undefined }) @@ -337,6 +287,70 @@ describe('sovrnBidAdapter', function() { const payload = JSON.parse(request.data) expect(payload.imp[0].bidfloor).to.equal(2.00) }) + describe('First Party Data', function () { + let sandbox + + beforeEach(function() { + sandbox = sinon.sandbox.create() + }) + afterEach(function() { + sandbox.restore() + }) + it('should provide first party data if provided', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const cfg = { + ortb2: { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + } + }; + return utils.deepAccess(cfg, key); + }); + const request = spec.buildRequests([adUnitBidRequest], bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.user.data).to.equal('some user data') + expect(payload.site.keywords).to.equal('test keyword') + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') + }) + it('should append impression first party data', function () { + const fpdBid = {...adUnitBidRequest} + fpdBid.ortb2Imp = { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + } + const request = spec.buildRequests([fpdBid], bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.data.pbadslot).to.equal('homepage-top-rect') + expect(payload.imp[0].ext.data.adUnitSpecificAttribute).to.equal('123') + }) + it('should not overwrite deals when impression fpd is present', function() { + const fpdBid = {...adUnitBidRequest} + fpdBid.params = {...adUnitBidRequest.params} + fpdBid.params.segments = 'seg1, seg2' + fpdBid.ortb2Imp = { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + } + const request = spec.buildRequests([fpdBid], bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.data.pbadslot).to.equal('homepage-top-rect') + expect(payload.imp[0].ext.data.adUnitSpecificAttribute).to.equal('123') + expect(payload.imp[0].ext.deals).to.deep.equal(['seg1', 'seg2']) + }) + }) }); describe('interpretResponse', function () { diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 927599ac986..5d7b32eaeeb 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -102,7 +102,7 @@ describe('the spotx adapter', function () { it('should build a very basic request', function() { var request = spec.buildRequests([bid], bidRequestObj)[0]; expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345'); + expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345?src_sys=prebid'); expect(request.bidRequest).to.equal(bidRequestObj); expect(request.data.id).to.equal(12345); expect(request.data.ext.wrap_response).to.equal(1); @@ -379,6 +379,32 @@ describe('the spotx adapter', function () { expect(request.data.site.page).to.equal('prebid.js'); }); + + it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'cache') { + return { + url: 'prebidCacheLocation', + ignoreBidderCacheKey: true + }; + } + if (key === 'cache.url') { + return 'prebidCacheLocation'; + } + if (key === 'cache.ignoreBidderCacheKey') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.ext.wrap_response).to.equal(0); + config.getConfig.restore(); + }); }); describe('interpretResponse', function() { diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js index a0765a0d396..2e1d65f0533 100644 --- a/test/spec/modules/sublimeBidAdapter_spec.js +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -18,6 +18,9 @@ describe('Sublime Adapter', function() { 'puid', 'trId', 'pbav', + 'pubpbv', + 'device', + 'pubtimeout', ]; beforeEach(function () { @@ -139,6 +142,7 @@ describe('Sublime Adapter', function() { describe('interpretResponse', function() { let serverResponse = { 'request_id': '3db3773286ee59', + 'sspname': 'foo', 'cpm': 0.5, 'ad': '', }; @@ -160,9 +164,10 @@ describe('Sublime Adapter', function() { creativeId: 1, dealId: 1, currency: 'USD', + sspname: 'foo', netRevenue: true, ttl: 600, - pbav: '0.7.0', + pbav: '0.7.1', ad: '', }, ]; @@ -173,6 +178,7 @@ describe('Sublime Adapter', function() { it('should get correct default size for 1x1', function() { let serverResponse = { 'requestId': 'xyz654_2', + 'sspname': 'sublime', 'cpm': 0.5, 'ad': '', }; @@ -204,7 +210,8 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.7.0', + pbav: '0.7.1', + sspname: 'sublime' }; expect(result[0]).to.deep.equal(expectedResponse); @@ -224,6 +231,7 @@ describe('Sublime Adapter', function() { it('should return bid with default value in response', function () { let serverResponse = { 'requestId': 'xyz654_2', + 'sspname': 'sublime', 'ad': '', }; @@ -251,10 +259,11 @@ describe('Sublime Adapter', function() { creativeId: 1, dealId: 1, currency: 'EUR', + sspname: 'sublime', netRevenue: true, ttl: 600, ad: '', - pbav: '0.7.0', + pbav: '0.7.1', }; expect(result[0]).to.deep.equal(expectedResponse); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 5ec8700ce89..688f1c2c090 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -1,6 +1,6 @@ import { assert, expect } from 'chai'; import { BANNER } from 'src/mediaTypes.js'; -import {config} from 'src/config.js'; +import { config } from 'src/config.js'; import { spec } from 'modules/synacormediaBidAdapter.js'; describe('synacormediaBidAdapter ', function () { @@ -55,7 +55,7 @@ describe('synacormediaBidAdapter ', function () { }); }); - describe('impression type', function() { + describe('impression type', function () { let nonVideoReq = { bidId: '9876abcd', sizes: [[300, 250], [300, 600]], @@ -107,7 +107,7 @@ describe('synacormediaBidAdapter ', function () { } }, }; - it('should return correct impression type video/banner', function() { + it('should return correct impression type video/banner', function () { assert.isFalse(spec.isVideoBid(nonVideoReq)); assert.isFalse(spec.isVideoBid(bannerReq)); assert.isTrue(spec.isVideoBid(videoReq)); @@ -126,12 +126,12 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]] + playerSize: [[640, 480]] } }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -149,7 +149,7 @@ describe('synacormediaBidAdapter ', function () { referer: 'https://localhost:9999/test/pages/video.html?pbjs_debug=true', reachedTop: true, numIframes: 0, - stack: [ 'https://localhost:9999/test/pages/video.html?pbjs_debug=true' ] + stack: ['https://localhost:9999/test/pages/video.html?pbjs_debug=true'] }, start: 1553624929700 }; @@ -427,7 +427,7 @@ describe('synacormediaBidAdapter ', function () { expect(spec.buildRequests([validBidRequest], null)).to.be.undefined; }); - it('should return empty impression when there is no valid sizes in bidrequest', function() { + it('should return empty impression when there is no valid sizes in bidrequest', function () { let validBidReqWithoutSize = { bidId: '9876abcd', sizes: [], @@ -480,12 +480,12 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]] + playerSize: [[640, 480]] } }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -534,7 +534,7 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]], + playerSize: [[640, 480]], startdelay: 1, linearity: 1, placement: 1, @@ -543,7 +543,7 @@ describe('synacormediaBidAdapter ', function () { }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -576,7 +576,51 @@ describe('synacormediaBidAdapter ', function () { } ]); }); - it('should contain the CCPA privacy string when UspConsent is in bidder request', function() { + it('should create params.video object if not present on bid request and move any video params in the mediaTypes object to it', function () { + let validBidRequestVideo = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + tagId: '1234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[ 640, 480 ]], + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', + sizes: [[ 640, 480 ]], + bidId: '2624fabbb078e8', + bidderRequestId: '117954d20d7c9c', + auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', + src: 'client', + bidRequestsCount: 1 + }; + + let req = spec.buildRequests([validBidRequestVideo], bidderRequest); + expect(req.data.imp).to.eql([ + { + video: { + h: 480, + pos: 0, + w: 640, + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'] + }, + id: 'v2624fabbb078e8-640x480', + tagid: '1234', + } + ]); + }); + it('should contain the CCPA privacy string when UspConsent is in bidder request', function () { // banner test let req = spec.buildRequests([validBidRequest], bidderRequestWithCCPA); expect(req).be.an('object'); @@ -652,7 +696,7 @@ describe('synacormediaBidAdapter ', function () { }); }); - describe('Bid Requests with schain object ', function() { + describe('Bid Requests with schain object ', function () { let validBidReq = { bidder: 'synacormedia', params: { @@ -803,7 +847,7 @@ describe('synacormediaBidAdapter ', function () { url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' }; let serverResponse; - beforeEach(function() { + beforeEach(function () { serverResponse = { body: { id: 'abc123', @@ -848,7 +892,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [], @@ -867,7 +911,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', - adId: '11339128001692337-9999-0', cpm: 0.45, width: 640, height: 480, @@ -877,6 +920,7 @@ describe('synacormediaBidAdapter ', function () { mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' }); @@ -888,7 +932,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -911,7 +954,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(2); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -925,7 +967,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp[1]).to.eql({ requestId: '9876abcd', - adId: '10865933907263800-9999-0', cpm: 1.99, width: 300, height: 600, @@ -962,7 +1003,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [], @@ -984,8 +1025,8 @@ describe('synacormediaBidAdapter ', function () { }); let resp = spec.interpretResponse(serverRespVideo, bidRequest); - sandbox.restore(); - expect(resp[0].videoCacheKey).to.not.exist; + sandbox.restore(); + expect(resp[0].videoCacheKey).to.not.exist; }); it('should use video bid request height and width if not present in response', function () { @@ -1022,7 +1063,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [] @@ -1037,7 +1078,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', - adId: '11339128001692337-9999-0', cpm: 0.45, width: 300, height: 250, @@ -1047,6 +1087,7 @@ describe('synacormediaBidAdapter ', function () { mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' }); @@ -1090,7 +1131,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: 'abc123', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 400, height: 350, diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index c4410d8ce5e..8197c4f44dd 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -1,36 +1,168 @@ import { assert } from 'chai'; -import {spec} from 'modules/tappxBidAdapter'; +import { spec } from 'modules/tappxBidAdapter'; -describe('Tappx adapter tests', function () { - describe('isBidRequestValid', function () { - let bid = { bidder: 'tappx', params: { host: 'testing.ssp.tappx.com', tappxkey: 'pub-1234-test-1234', endpoint: 'ZZ1234PBJS', bidfloor: 0.005 } }; +const c_BIDREQUEST = { + data: { + }, + bids: [ + { + bidder: 'tappx', + params: { + host: 'testing.ssp.tappx.com\/rtb\/v2\/', + tappxkey: 'pub-1234-android-1234', + endpoint: 'ZZ1234PBJS', + bidfloor: 0.05 + }, + crumbs: { + pubcid: 'df2144f7-673f-4440-83f5-cd4a73642d99' + }, + fpd: { + context: { + adServer: { + name: 'gam', + adSlot: '/19968336/header-bid-tag-0' + }, + pbAdSlot: '/19968336/header-bid-tag-0', + }, + }, + mediaTypes: { + banner: { + sizes: [ + [ + 320, + 480 + ] + ] + } + }, + adUnitCode: 'div-1', + transactionId: '47dd44e8-e7db-417c-a8f1-621a2e1a117d', + sizes: [ + [ + 320, + 480 + ] + ], + bidId: '2170932097e505', + bidderRequestId: '140ba7a1ab7aeb', + auctionId: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + } + ] +}; +const c_SERVERRESPONSE_B = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "\t", + w: 320, + h: 480, + lurl: 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_SERVERRESPONSE_V = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "Tappx<\/AdSystem>Tappx<\/AdTitle><\/Impression><\/Error>00:00:22<\/Duration><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/TrackingEvents><\/ClickThrough><\/ClickTracking><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + 'lurl': 'https:\/\/ssp.api.tappx.com\/rtb\/RTBv2Loss?id=5001829913749291152&ep=VZ12TESTCTV&au=test&bu=localhost&sz=6x6&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"e807363f-3095-43a8-a4a6-f44196cb7318","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"w":320,"h":250,"mimes":["video/mp4","application/javascript"]},"id":"28f49c71b13f2f","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"VZ12TESTCTV","host":"testing.ssp.tappx.com/rtb/v2/"}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'context': 'instream', 'mimes': ['video/mp4', 'application/javascript'], 'playerSize': [[320, 250]]}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +describe('Tappx bid adapter', function () { + /** + * IS REQUEST VALID + */ + describe('isBidRequestValid', function () { it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); + assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); it('should return false when required params are missing', function () { - const bid = { - host: 'testing.ssp.tappx.com' - }; - assert.isFalse(spec.isBidRequestValid(bid)); + let badBidRequest = c_BIDREQUEST; + delete badBidRequest.bids[0].params.tappxkey; + delete badBidRequest.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); + }); + + it('should return false for not instream requests', function () { + let badBidRequest_v = c_BIDDERREQUEST_V; + delete badBidRequest_v.bids.mediaTypes.banner; + badBidRequest_v.bids.mediaTypes.video = {}; + badBidRequest_v.bids.mediaTypes.video.context = 'outstream'; + badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; + assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); }); }); + /** + * BUILD REQUEST TEST + */ describe('buildRequest', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; // Web Test - let validBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 480]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + let validBidRequests = c_VALIDBIDREQUESTS; + let validBidRequests_V = c_VALIDBIDREQUESTS; // App Test - let validAppBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5], 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 50]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] - let bidderRequest = {'bidderCode': 'tappx', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'bidderRequestId': '1ae5c6a02684df', 'bids': [{'bidder': 'tappx', 'params': {'host': 'tests.tappx.com', 'tappxkey': 'pub-1234-test-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'banner-ad-div', 'transactionId': 'c44cdbde-ab6d-47a0-8dde-6b4ff7909a35', 'sizes': [[320, 50]], 'bidId': '2e3a5feb30cfe4', 'bidderRequestId': '1ae5c6a02684df', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1611308859094, 'timeout': 700, 'refererInfo': {'referer': 'http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': consentString, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; + let validAppBidRequests = c_VALIDBIDAPPREQUESTS; + + let bidderRequest = c_BIDDERREQUEST_B; + let bidderRequest_V = c_BIDDERREQUEST_V; it('should add gdpr/usp consent information to the request', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = JSON.parse(request[0].data); expect(payload.regs.gdpr).to.exist.and.to.be.true; - expect(payload.regs.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.consent).to.exist.and.to.equal(c_CONSENTSTRING); expect(payload.regs.ext.us_privacy).to.exist; }); @@ -48,6 +180,28 @@ describe('Tappx adapter tests', function () { expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); }); + it('should properly build a video request', function () { + const request = spec.buildRequests(validBidRequests_V, bidderRequest_V); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should set user eids array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + const data = JSON.parse(request[0].data); + expect(data.user.ext.eids, data).to.not.be.null; + expect(data.user.ext.eids[0]).to.have.keys(['source', 'uids']); + }); + it('should properly build a banner request with app params', function () { const request = spec.buildRequests(validAppBidRequests, bidderRequest); expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); @@ -61,26 +215,82 @@ describe('Tappx adapter tests', function () { expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); }); + + it('should properly build a ext optional object', function() { + let extBidRequest = c_VALIDBIDREQUESTS; + extBidRequest[0].params.ext = {'optionalData': '1234'}; + let extBidderRequest = c_BIDDERREQUEST_B; + extBidderRequest.bids[0].ext = {'optionalData': '1234'}; + + const request = spec.buildRequests(extBidRequest, extBidderRequest); + const data = JSON.parse(request[0].data); + expect(data.imp[0].ext.bidder.ext).to.be.an('object'); + expect(data.imp[0].ext.bidder.ext.optionalData).to.be.equal('1234'); + }); + + it('should ignore ext optional if is not a object', function() { + let badExtBidRequest = c_VALIDBIDREQUESTS; + badExtBidRequest[0].params.ext = 'stringValue'; + let badExtBidderRequest = c_BIDDERREQUEST_B; + badExtBidderRequest.bids[0].ext = 'stringValue'; + + const request = spec.buildRequests(badExtBidRequest, badExtBidderRequest); + const data = JSON.parse(request[0].data); + expect(data.imp[0].ext.bidder.ext).not.to.be.an('string'); + expect(data.imp[0].ext.bidder.ext).to.be.an('undefined'); + expect(data.imp[0].ext.bidder).to.not.have.property('ext') + }); }); + /** + * INTERPRET RESPONSE TESTS + */ describe('interpretResponse', function () { - const bidRequest = { - data: {}, - bids: [ {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.05, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '47dd44e8-e7db-417c-a8f1-621a2e1a117d', 'sizes': [[320, 480]], 'bidId': '2170932097e505', 'bidderRequestId': '140ba7a1ab7aeb', 'auctionId': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0} ] - }; - - const serverResponse = {'body': {'id': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'bidid': 'bid3811165568213389257', 'seatbid': [{'seat': '1', 'group': 0, 'bid': [{'id': '3811165568213389257', 'impid': 1, 'price': 0.05, 'adm': "\t", 'w': 320, 'h': 480, 'lurl': 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', 'cid': '01744fbb521e9fb10ffea926190effea', 'crid': 'a13cf884e66e7c660afec059c89d98b6', 'adomain': []}]}], 'cur': 'USD'}, 'headers': {}}; - it('receive reponse with single placement', function () { - const bids = spec.interpretResponse(serverResponse, bidRequest); + it('receive banner reponse with single placement', function () { + const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^