From 5b4987d567d2e79add9b05bd74672c3bf3b7b6fa Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 17 Jun 2022 05:51:15 -0700 Subject: [PATCH] Prebid core & PBS adapter: Feature tags and optional compilation of native support (#8219) * Upgrade webpack to 5; gulp build works * Fix karma, except events * Uniform access to events, import * or require (import * from 'events.js' / import events from 'events.js' return different objects, which is a problem for stubbing) * Fix (?) adapters that use `this` inappropriately * Update webpack-bundle-analyzer * Fix warnings * Enable tree shaking * Set webpack mode 'none' (or else tests fail (!)) * Update coreJS version in babelrc - https://github.com/prebid/Prebid.js/issues/7943 * Use babel to translate to commonjs only for unit tests; enable production mode * Define feature flags at compile time - starting with just "NATIVE" * Add build-bundle-verbose * Run tests with all features disabled * Fix build-bundle-verbose * Tag native#nativeAdapters * Merge master * Add fsevents as optional dep * Adjust syntax for node 12 --- .eslintrc.js | 3 +- features.json | 3 + gulpHelpers.js | 8 +- gulpfile.js | 80 ++++---- karma.conf.maker.js | 8 +- karmaRunner.js | 23 +++ modules/prebidServerBidAdapter/index.js | 29 +-- modules/sizeMappingV2.js | 4 +- plugins/pbjsGlobals.js | 26 +++ src/adapterManager.js | 8 +- src/adapters/bidderFactory.js | 2 +- src/auction.js | 10 +- src/prebid.js | 7 +- src/secureCreatives.js | 7 +- src/targeting.js | 9 +- .../modules/prebidServerBidAdapter_spec.js | 180 +++++++++--------- test/spec/modules/sizeMappingV2_spec.js | 3 + test/spec/unit/core/adapterManager_spec.js | 42 ++-- test/spec/unit/core/bidderFactory_spec.js | 134 ++++++------- test/spec/unit/core/targeting_spec.js | 42 ++-- test/spec/unit/pbjs_api_spec.js | 106 ++++++----- test/spec/unit/secureCreatives_spec.js | 4 + webpack.conf.js | 6 +- 23 files changed, 436 insertions(+), 308 deletions(-) create mode 100644 features.json create mode 100644 karmaRunner.js diff --git a/.eslintrc.js b/.eslintrc.js index 95515e8ba98..06a5e81d9f5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,8 @@ module.exports = { globals: { '$$PREBID_GLOBAL$$': false, 'BROWSERSTACK_USERNAME': false, - 'BROWSERSTACK_KEY': false + 'BROWSERSTACK_KEY': false, + 'FEATURES': 'readonly', }, // use babel as parser for fancy syntax parser: '@babel/eslint-parser', diff --git a/features.json b/features.json new file mode 100644 index 00000000000..c0f7e4d75a9 --- /dev/null +++ b/features.json @@ -0,0 +1,3 @@ +[ + "NATIVE" +] diff --git a/gulpHelpers.js b/gulpHelpers.js index 020b1e8fb1a..e603ece7310 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -206,5 +206,11 @@ module.exports = { } return options; - } + }, + getDisabledFeatures() { + return (argv.disable || '') + .split(',') + .map((s) => s.trim()) + .filter((s) => s); + }, }; diff --git a/gulpfile.js b/gulpfile.js index 6e2c0c527c4..3dbbf230e6b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,8 +9,6 @@ var connect = require('gulp-connect'); var webpack = require('webpack'); var webpackStream = require('webpack-stream'); var gulpClean = require('gulp-clean'); -var KarmaServer = require('karma').Server; -var karmaConfMaker = require('./karma.conf.maker.js'); var opens = require('opn'); var webpackConfig = require('./webpack.conf.js'); var helpers = require('./gulpHelpers.js'); @@ -32,7 +30,8 @@ var prebid = require('./package.json'); var port = 9999; const INTEG_SERVER_HOST = argv.host ? argv.host : 'localhost'; const INTEG_SERVER_PORT = 4444; -const { spawn } = require('child_process'); +const { spawn, fork } = require('child_process'); +const TerserPlugin = require('terser-webpack-plugin'); // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules var explicitModules = [ @@ -140,21 +139,23 @@ function makeDevpackPkg() { .pipe(connect.reload()); } -function makeWebpackPkg() { - var cloned = _.cloneDeep(webpackConfig); +function makeWebpackPkg(extraConfig = {}) { + var cloned = _.merge(_.cloneDeep(webpackConfig), extraConfig); if (!argv.sourceMaps) { delete cloned.devtool; } - var externalModules = helpers.getArgModules(); + return function buildBundle() { + var externalModules = helpers.getArgModules(); - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); + const analyticsSources = helpers.getAnalyticsSources(); + const moduleSources = helpers.getModulePaths(externalModules); - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) - .pipe(gulp.dest('build/dist')); + return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) + .pipe(helpers.nameModules(externalModules)) + .pipe(webpackStream(cloned, webpack)) + .pipe(gulp.dest('build/dist')); + } } function getModulesListToAddInBanner(modules) { @@ -272,9 +273,11 @@ function bundle(dev, moduleArr) { function testTaskMaker(options = {}) { ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { - options[opt] = options[opt] || argv[opt]; + options[opt] = options.hasOwnProperty(opt) ? options[opt] : argv[opt]; }) + options.disableFeatures = options.disableFeatures || helpers.getDisabledFeatures(); + return function test(done) { if (options.notest) { done(); @@ -295,14 +298,7 @@ function testTaskMaker(options = {}) { process.exit(1); }); } else { - var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file); - - var browserOverride = helpers.parseBrowserArgs(argv); - if (browserOverride.length > 0) { - karmaConf.browsers = browserOverride; - } - - new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + runKarma(options, done) } } } @@ -329,25 +325,24 @@ function runWebdriver({file}) { return execa(wdioCmd, wdioOpts, { stdio: 'inherit' }); } -function newKarmaCallback(done) { - return function (exitCode) { +function runKarma(options, done) { + // the karma server appears to leak memory; starting it multiple times in a row will run out of heap + // here we run it in a separate process to bypass the problem + options = Object.assign({browsers: helpers.parseBrowserArgs(argv)}, options) + const child = fork('./karmaRunner.js'); + child.on('exit', (exitCode) => { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); - if (argv.browserstack) { - process.exit(exitCode); - } } else { done(); - if (argv.browserstack) { - process.exit(exitCode); - } } - } + }) + child.send(options); } // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { - new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); + runKarma({coverage: true, browserstack: false, watch: false, file: argv.file}, done); } function coveralls() { // 2nd arg is a dependency: 'test' must be finished @@ -425,11 +420,30 @@ gulp.task(clean); gulp.task(escapePostbidConfig); gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true))); -gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false))); +gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg(), gulpBundle.bind(null, false))); +// build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects +// of dead code elimination. +gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ + optimization: { + minimizer: [ + new TerserPlugin({ + parallel: true, + terserOptions: { + mangle: false, + format: { + comments: 'all' + } + }, + extractComments: false, + }), + ], + } +}), gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); -gulp.task('test', gulp.series(clean, lint, 'test-only')); +gulp.task('test-all-features-disabled', testTaskMaker({disableFeatures: require('./features.json'), oneBrowser: 'chrome', watch: false})) +gulp.task('test', gulp.series(clean, lint, gulp.series('test-all-features-disabled', 'test-only'))); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 2a174530352..b68b9dfebb6 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -7,7 +7,7 @@ var _ = require('lodash'); var webpackConf = require('./webpack.conf.js'); var karmaConstants = require('karma').constants; -function newWebpackConfig(codeCoverage) { +function newWebpackConfig(codeCoverage, disableFeatures) { // Make a clone here because we plan on mutating this object, and don't want parallel tasks to trample each other. var webpackConfig = _.cloneDeep(webpackConf); @@ -22,7 +22,7 @@ function newWebpackConfig(codeCoverage) { .flatMap((r) => r.use) .filter((use) => use.loader === 'babel-loader') .forEach((use) => { - use.options = babelConfig({test: true}); + use.options = babelConfig({test: true, disableFeatures}); }); if (codeCoverage) { @@ -117,8 +117,8 @@ function setBrowsers(karmaConf, browserstack) { } } -module.exports = function(codeCoverage, browserstack, watchMode, file) { - var webpackConfig = newWebpackConfig(codeCoverage); +module.exports = function(codeCoverage, browserstack, watchMode, file, disableFeatures) { + var webpackConfig = newWebpackConfig(codeCoverage, disableFeatures); var plugins = newPluginsArray(browserstack); var files = file ? ['test/test_deps.js', file] : ['test/test_index.js']; diff --git a/karmaRunner.js b/karmaRunner.js new file mode 100644 index 00000000000..96259069966 --- /dev/null +++ b/karmaRunner.js @@ -0,0 +1,23 @@ +const karma = require('karma'); +const process = require('process'); +const karmaConfMaker = require('./karma.conf.maker.js'); + +process.on('message', function(options) { + try { + let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, options.file, options.disableFeatures); + if (options.browsers && options.browsers.length) { + cfg.browsers = options.browsers; + } + if (options.oneBrowser) { + cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]] + } + cfg = karma.config.parseConfig(null, cfg); + new karma.Server(cfg, (exitCode) => { + process.exit(exitCode); + }).start(); + } catch (e) { + // eslint-disable-next-line + console.error(e); + process.exit(1); + } +}); diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index ecf13ae5659..dcc39c75715 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -438,18 +438,19 @@ let nativeEventTrackerMethodMap = { js: 2 }; -// enable reverse lookup -[ - nativeDataIdMap, - nativeImgIdMap, - nativeEventTrackerEventMap, - nativeEventTrackerMethodMap -].forEach(map => { - Object.keys(map).forEach(key => { - map[map[key]] = key; +if (FEATURES.NATIVE) { + // enable reverse lookup + [ + nativeDataIdMap, + nativeImgIdMap, + nativeEventTrackerEventMap, + nativeEventTrackerMethodMap + ].forEach(map => { + Object.keys(map).forEach(key => { + map[map[key]] = key; + }); }); -}); - +} /* * Protocol spec for OpenRTB endpoint * e.g., https:///v1/openrtb2/auction @@ -558,7 +559,7 @@ Object.assign(ORTB2.prototype, { const nativeParams = adUnit.nativeParams; let nativeAssets; - if (nativeParams) { + if (FEATURES.NATIVE && nativeParams) { let idCounter = -1; try { nativeAssets = nativeAssetCache[impressionId] = Object.keys(nativeParams).reduce((assets, type) => { @@ -683,7 +684,7 @@ Object.assign(ORTB2.prototype, { } } - if (nativeAssets) { + if (FEATURES.NATIVE && nativeAssets) { try { mediaTypes['native'] = { request: JSON.stringify({ @@ -1036,7 +1037,7 @@ Object.assign(ORTB2.prototype, { if (bid.adm) { bidObject.vastXml = bid.adm; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - } else if (deepAccess(bid, 'ext.prebid.type') === NATIVE) { + } else if (FEATURES.NATIVE && deepAccess(bid, 'ext.prebid.type') === NATIVE) { bidObject.mediaType = NATIVE; let adm; if (typeof bid.adm === 'string') { diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 405799813eb..4a1518ab72e 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -129,7 +129,7 @@ export function checkAdUnitSetupHook(adUnits) { Verify that 'config.active' is a 'boolean'. If not, return 'false'. */ - if (mediaType === 'native') { + if (FEATURES.NATIVE && mediaType === 'native') { if (typeof config[propertyName] !== 'boolean') { logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); isValid = false; @@ -206,7 +206,7 @@ export function checkAdUnitSetupHook(adUnits) { } } - if (mediaTypes.native) { + if (FEATURES.NATIVE && mediaTypes.native) { // Apply the old native checks validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index f611db1444c..717eef2d6b6 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -2,6 +2,20 @@ let t = require('@babel/core').types; let prebid = require('../package.json'); const path = require('path'); +const allFeatures = new Set(require('../features.json')); + +const FEATURES_GLOBAL = 'FEATURES'; + +function featureMap(disable = []) { + disable = disable.map((s) => s.toUpperCase()); + disable.forEach((f) => { + if (!allFeatures.has(f)) { + throw new Error(`Unrecognized feature: ${f}`) + } + }); + disable = new Set(disable); + return Object.fromEntries([...allFeatures.keys()].map((f) => [f, !disable.has(f)])); +} function getNpmVersion(version) { try { @@ -13,6 +27,7 @@ function getNpmVersion(version) { module.exports = function(api, options) { const pbGlobal = options.globalVarName || prebid.globalVarName; + const features = featureMap(options.disableFeatures); let replace = { '$prebid.version$': prebid.version, '$$PREBID_GLOBAL$$': pbGlobal, @@ -91,6 +106,17 @@ module.exports = function(api, options) { } } }); + }, + MemberExpression(path) { + if ( + t.isIdentifier(path.node.object) && + path.node.object.name === FEATURES_GLOBAL && + !path.scope.hasBinding(FEATURES_GLOBAL) && + t.isIdentifier(path.node.property) && + features.hasOwnProperty(path.node.property.name) + ) { + path.replaceWith(t.booleanLiteral(features[path.node.property.name])); + } } } }; diff --git a/src/adapterManager.js b/src/adapterManager.js index 311e936688a..a707cc473d6 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -211,7 +211,9 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a * @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue} */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); - decorateAdUnitsWithNativeParams(adUnits); + if (FEATURES.NATIVE) { + decorateAdUnitsWithNativeParams(adUnits); + } adUnits = setupAdUnitMediaTypes(adUnits, labels); let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs); @@ -425,7 +427,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request function getSupportedMediaTypes(bidderCode) { let supportedMediaTypes = []; if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); - if (includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); + if (FEATURES.NATIVE && includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); return supportedMediaTypes; } @@ -439,7 +441,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); } - if (includes(supportedMediaTypes, 'native')) { + if (FEATURES.NATIVE && includes(supportedMediaTypes, 'native')) { nativeAdapters.push(bidderCode); } } else { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 20e7c11c28f..e798c63d753 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -542,7 +542,7 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { return false; } - if (bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { + if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { logError(errorMessage('Native bid missing some required properties.')); return false; } diff --git a/src/auction.js b/src/auction.js index 1f24c7e6557..fb9c6302e74 100644 --- a/src/auction.js +++ b/src/auction.js @@ -93,6 +93,14 @@ const outstandingRequests = {}; const sourceInfo = {}; const queuedCalls = []; +/** + * Clear global state for tests + */ +export function resetAuctionState() { + queuedCalls.length = 0; + [outstandingRequests, sourceInfo].forEach((ob) => Object.keys(ob).forEach((k) => { delete ob[k] })); +} + /** * Creates new auction instance * @@ -757,7 +765,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, {index = aucti } // set native key value targeting - if (custBidObj['native']) { + if (FEATURES.NATIVE && custBidObj['native']) { keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } diff --git a/src/prebid.js b/src/prebid.js index f6df1376708..44e377c5753 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -188,10 +188,13 @@ export const adUnitSetupChecks = { validateAdUnit, validateBannerMediaType, validateVideoMediaType, - validateNativeMediaType, validateSizes }; +if (FEATURES.NATIVE) { + Object.assign(adUnitSetupChecks, {validateNativeMediaType}); +} + export const checkAdUnitSetup = hook('sync', function (adUnits) { const validatedAdUnits = []; @@ -212,7 +215,7 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } - if (mediaTypes.native) { + if (FEATURES.NATIVE && mediaTypes.native) { validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 5cfa25fbbc8..7eacb3c3fc0 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -18,10 +18,15 @@ const STALE_RENDER = constants.EVENTS.STALE_RENDER; const HANDLER_MAP = { 'Prebid Request': handleRenderRequest, - 'Prebid Native': handleNativeRequest, 'Prebid Event': handleEventRequest, } +if (FEATURES.NATIVE) { + Object.assign(HANDLER_MAP, { + 'Prebid Native': handleNativeRequest, + }) +} + export function listenMessagesFromCreative() { window.addEventListener('message', receiveMessage, false); } diff --git a/src/targeting.js b/src/targeting.js index 8584927c468..cb10b3f0121 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -176,7 +176,7 @@ export function newTargeting(auctionManager) { */ function getDealBids(adUnitCodes, bidsReceived) { if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { - const standardKeys = TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); // we only want the top bid from bidders who have multiple entries per ad unit code const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm); @@ -583,7 +583,10 @@ export function newTargeting(auctionManager) { } function getCustomKeys() { - let standardKeys = getStandardKeys().concat(NATIVE_TARGETING_KEYS); + let standardKeys = getStandardKeys(); + if (FEATURES.NATIVE) { + standardKeys = standardKeys.concat(NATIVE_TARGETING_KEYS); + } return function(key) { return standardKeys.indexOf(key) === -1; } @@ -623,7 +626,7 @@ export function newTargeting(auctionManager) { * @return {targetingArray} all non-winning bids targeting */ function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { - const standardKeys = TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 7aa6a5c7f42..6793bb988bf 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1228,77 +1228,79 @@ describe('S2S Adapter', function () { }); }); - it('adds native request for OpenRTB', function () { - const _config = { - s2sConfig: CONFIG - }; + if (FEATURES.NATIVE) { + it('adds native request for OpenRTB', function () { + const _config = { + s2sConfig: CONFIG + }; - config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].native).to.deep.equal({ - request: JSON.stringify({ - 'context': 1, - 'plcmttype': 1, - 'eventtrackers': [{ - event: 1, - methods: [1] - }], - 'assets': [ - { - 'required': 1, - 'id': 0, - 'title': { - 'len': 800 - } - }, - { - 'required': 1, - 'id': 1, - 'img': { - 'type': 3, - 'w': 989, - 'h': 742 - } - }, - { - 'required': 1, - 'id': 2, - 'img': { - 'type': 1, - 'wmin': 10, - 'hmin': 10, - 'ext': { - 'aspectratios': ['1:1'] + expect(requestBid.imp[0].native).to.deep.equal({ + request: JSON.stringify({ + 'context': 1, + 'plcmttype': 1, + 'eventtrackers': [{ + event: 1, + methods: [1] + }], + 'assets': [ + { + 'required': 1, + 'id': 0, + 'title': { + 'len': 800 + } + }, + { + 'required': 1, + 'id': 1, + 'img': { + 'type': 3, + 'w': 989, + 'h': 742 + } + }, + { + 'required': 1, + 'id': 2, + 'img': { + 'type': 1, + 'wmin': 10, + 'hmin': 10, + 'ext': { + 'aspectratios': ['1:1'] + } + } + }, + { + 'required': 1, + 'id': 3, + 'data': { + 'type': 1 } } - }, - { - 'required': 1, - 'id': 3, - 'data': { - 'type': 1 - } - } - ] - }), - ver: '1.2' + ] + }), + ver: '1.2' + }); }); - }); - it('should not include ext.aspectratios if adunit\'s aspect_ratios do not define radio_width and ratio_height', () => { - const req = deepClone(REQUEST); - req.ad_units[0].mediaTypes.native.icon.aspect_ratios[0] = { 'min_width': 1, 'min_height': 2 }; - prepRequest(req); - adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); - const nativeReq = JSON.parse(JSON.parse(server.requests[0].requestBody).imp[0].native.request); - const icons = nativeReq.assets.map((a) => a.img).filter((img) => img && img.type === 1); - expect(icons).to.have.length(1); - expect(icons[0].hmin).to.equal(2); - expect(icons[0].wmin).to.equal(1); - expect(deepAccess(icons[0], 'ext.aspectratios')).to.be.undefined; - }) + it('should not include ext.aspectratios if adunit\'s aspect_ratios do not define radio_width and ratio_height', () => { + const req = deepClone(REQUEST); + req.ad_units[0].mediaTypes.native.icon.aspect_ratios[0] = {'min_width': 1, 'min_height': 2}; + prepRequest(req); + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const nativeReq = JSON.parse(JSON.parse(server.requests[0].requestBody).imp[0].native.request); + const icons = nativeReq.assets.map((a) => a.img).filter((img) => img && img.type === 1); + expect(icons).to.have.length(1); + expect(icons[0].hmin).to.equal(2); + expect(icons[0].wmin).to.equal(1); + expect(deepAccess(icons[0], 'ext.aspectratios')).to.be.undefined; + }) + } it('adds site if app is not present', function () { const _config = { @@ -2616,33 +2618,35 @@ describe('S2S Adapter', function () { expect(response).to.have.property('pbsBidId', '654321'); }); - it('handles OpenRTB native responses', function () { - const stub = sinon.stub(auctionManager, 'index'); - stub.get(() => stubAuctionIndex({ adUnits: REQUEST.ad_units })); - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } - }); - config.setConfig({ s2sConfig }); + if (FEATURES.NATIVE) { + it('handles OpenRTB native responses', function () { + const stub = sinon.stub(auctionManager, 'index'); + stub.get(() => stubAuctionIndex({adUnits: REQUEST.ad_units})); + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } + }); + config.setConfig({s2sConfig}); - const s2sBidRequest = utils.deepClone(REQUEST); - s2sBidRequest.s2sConfig = s2sConfig; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('adm').deep.equal(RESPONSE_OPENRTB_NATIVE.seatbid[0].bid[0].adm); - expect(response).to.have.property('mediaType', 'native'); - expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('requestId', '123'); - expect(response).to.have.property('cpm', 10); + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('adm').deep.equal(RESPONSE_OPENRTB_NATIVE.seatbid[0].bid[0].adm); + expect(response).to.have.property('mediaType', 'native'); + expect(response).to.have.property('bidderCode', 'appnexus'); + expect(response).to.have.property('requestId', '123'); + expect(response).to.have.property('cpm', 10); - stub.restore(); - }); + stub.restore(); + }); + } it('does not (by default) allow bids that were not requested', function () { config.setConfig({ s2sConfig: CONFIG }); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 9bbd472c7e0..27fd7a33c14 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -616,6 +616,9 @@ describe('sizeMappingV2', function () { }); describe('native mediaTypes checks', function () { + if (!FEATURES.NATIVE) { + return; + } beforeEach(function () { sinon.spy(adUnitSetupChecks, 'validateNativeMediaType'); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index dcbc098305c..a437e938327 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1671,25 +1671,29 @@ describe('adapterManager tests', function () { }) }); - it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { - function beforeReqBids(adUnits) { - adUnits.forEach(adUnit => { - adUnit.mediaTypes.native = { - type: 'image', - } - }) - } - events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); - expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; - }); + if (FEATURES.NATIVE) { + it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { + function beforeReqBids(adUnits) { + adUnits.forEach(adUnit => { + adUnit.mediaTypes.native = { + type: 'image', + } + }) + } + + events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; + }); + } it('should make separate bidder request objects for each bidder', () => { adUnits = [utils.deepClone(getAdUnits()[0])]; diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index a1acbb56de9..be68fc03765 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -874,85 +874,87 @@ describe('validate bid response: ', function () { indexStub.restore; }); - it('should add native bids that do have required assets', function () { - adUnits = [{ - transactionId: 'au', - nativeParams: { - title: {'required': true}, - } - }] - let bidRequest = { - bids: [{ - bidId: '1', - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', + if (FEATURES.NATIVE) { + it('should add native bids that do have required assets', function () { + adUnits = [{ transactionId: 'au', - params: { - param: 5 - }, - mediaType: 'native', + nativeParams: { + title: {'required': true}, + } }] - }; + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; - let bids1 = Object.assign({}, - bids[0], - { - 'mediaType': 'native', - 'native': { - 'title': 'Native Creative', - 'clickUrl': 'https://www.link.example', + let bids1 = Object.assign({}, + bids[0], + { + 'mediaType': 'native', + 'native': { + 'title': 'Native Creative', + 'clickUrl': 'https://www.link.example', + } } - } - ); + ); - const bidder = newBidder(spec); + const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - expect(logErrorSpy.callCount).to.equal(0); - }); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); - it('should not add native bids that do not have required assets', function () { - adUnits = [{ - transactionId: 'au', - nativeParams: { - title: {'required': true}, - }, - }]; - let bidRequest = { - bids: [{ - bidId: '1', - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', + it('should not add native bids that do not have required assets', function () { + adUnits = [{ transactionId: 'au', - params: { - param: 5 + nativeParams: { + title: {'required': true}, }, - mediaType: 'native', - }] - }; - let bids1 = Object.assign({}, - bids[0], - { - bidderCode: CODE, - mediaType: 'native', - native: { - title: undefined, - clickUrl: 'https://www.link.example', + }]; + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: CODE, + mediaType: 'native', + native: { + title: undefined, + clickUrl: 'https://www.link.example', + } } - } - ); + ); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); - expect(logErrorSpy.callCount).to.equal(1); - }); + expect(addBidResponseStub.calledOnce).to.equal(false); + expect(logErrorSpy.callCount).to.equal(1); + }); + } it('should add bid when renderer is present on outstream bids', function () { adUnits = [{ diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 53aa3b90fa8..4585ddbfaaf 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -599,7 +599,9 @@ describe('targeting tests', function () { } }); const defaultKeys = new Set(Object.values(CONSTANTS.DEFAULT_TARGETING_KEYS)); - Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + if (FEATURES.NATIVE) { + Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + } const expectedKeys = new Set(); bidsReceived @@ -802,26 +804,28 @@ describe('targeting tests', function () { expect(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]); }); - it('ensures keys are properly generated when enableSendAllBids is true and multiple bidders use native', function() { - const nativeAdUnitCode = '/19968336/prebid_native_example_1'; - enableSendAllBids = true; + if (FEATURES.NATIVE) { + it('ensures keys are properly generated when enableSendAllBids is true and multiple bidders use native', function () { + const nativeAdUnitCode = '/19968336/prebid_native_example_1'; + enableSendAllBids = true; - // update mocks for this test to return native bids - amBidsReceivedStub.callsFake(function() { - return [nativeBid1, nativeBid2]; - }); - amGetAdUnitsStub.callsFake(function() { - return [nativeAdUnitCode]; - }); + // update mocks for this test to return native bids + amBidsReceivedStub.callsFake(function () { + return [nativeBid1, nativeBid2]; + }); + amGetAdUnitsStub.callsFake(function () { + return [nativeAdUnitCode]; + }); - let targeting = targetingInstance.getAllTargeting([nativeAdUnitCode]); - expect(targeting[nativeAdUnitCode].hb_native_image).to.equal(nativeBid1.native.image.url); - expect(targeting[nativeAdUnitCode].hb_native_linkurl).to.equal(nativeBid1.native.clickUrl); - expect(targeting[nativeAdUnitCode].hb_native_title).to.equal(nativeBid1.native.title); - expect(targeting[nativeAdUnitCode].hb_native_image_dgad).to.exist.and.to.equal(nativeBid2.native.image.url); - expect(targeting[nativeAdUnitCode].hb_pb_dgads).to.exist.and.to.equal(nativeBid2.pbMg); - expect(targeting[nativeAdUnitCode].hb_native_body_appne).to.exist.and.to.equal(nativeBid1.native.body); - }); + let targeting = targetingInstance.getAllTargeting([nativeAdUnitCode]); + expect(targeting[nativeAdUnitCode].hb_native_image).to.equal(nativeBid1.native.image.url); + expect(targeting[nativeAdUnitCode].hb_native_linkurl).to.equal(nativeBid1.native.clickUrl); + expect(targeting[nativeAdUnitCode].hb_native_title).to.equal(nativeBid1.native.title); + expect(targeting[nativeAdUnitCode].hb_native_image_dgad).to.exist.and.to.equal(nativeBid2.native.image.url); + expect(targeting[nativeAdUnitCode].hb_pb_dgads).to.exist.and.to.equal(nativeBid2.pbMg); + expect(targeting[nativeAdUnitCode].hb_native_body_appne).to.exist.and.to.equal(nativeBid1.native.body); + }); + } it('does not include adpod type bids in the getBidsReceived results', function () { let adpodBid = utils.deepClone(bid1); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index fa87d7530cd..5fc171cb440 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -21,6 +21,7 @@ import * as pbjsModule from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; import $$PREBID_GLOBAL$$ from 'src/prebid.js'; +import {resetAuctionState} from 'src/auction.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -207,6 +208,7 @@ describe('Unit: Prebid Module', function () { synchronizePromise(promiseSandbox); bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true); configObj.setConfig({ useBidCache: true }); + resetAuctionState(); }); afterEach(function() { @@ -2091,59 +2093,61 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - let badNativeImgSize = [{ - code: 'testb4', - bids: [], - mediaTypes: { - native: { - image: { - sizes: '300x250' + if (FEATURES.NATIVE) { + let badNativeImgSize = [{ + code: 'testb4', + bids: [], + mediaTypes: { + native: { + image: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgSize - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); - - let badNativeImgAspRat = [{ - code: 'testb5', - bids: [], - mediaTypes: { - native: { - image: { - aspect_ratios: '300x250' + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgSize + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); + + let badNativeImgAspRat = [{ + code: 'testb5', + bids: [], + mediaTypes: { + native: { + image: { + aspect_ratios: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgAspRat - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); - - let badNativeIcon = [{ - code: 'testb6', - bids: [], - mediaTypes: { - native: { - icon: { - sizes: '300x250' + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgAspRat + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); + + let badNativeIcon = [{ + code: 'testb6', + bids: [], + mediaTypes: { + native: { + icon: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeIcon - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeIcon + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + } }); it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { @@ -2176,6 +2180,9 @@ describe('Unit: Prebid Module', function () { }); describe('multiformat requests', function () { + if (!FEATURES.NATIVE) { + return; + } let adUnits; beforeEach(function () { @@ -2227,6 +2234,9 @@ describe('Unit: Prebid Module', function () { }); describe('part 2', function () { + if (!FEATURES.NATIVE) { + return; + } let spyCallBids; let createAuctionStub; let adUnits; @@ -2294,7 +2304,7 @@ describe('Unit: Prebid Module', function () { it('splits native type to individual native assets', function () { let adUnits = [{ code: 'adUnit-code', - mediaTypes: { native: { type: 'image' } }, + mediaTypes: {native: {type: 'image'}}, bids: [ {bidder: 'appnexus', params: {placementId: 'id'}} ] diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index e3dc21ffd92..39fa9b9250f 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -308,6 +308,10 @@ describe('secureCreatives', () => { }); describe('Prebid Native', function() { + if (!FEATURES.NATIVE) { + return; + } + it('Prebid native should render', function () { pushBidResponseToAuction({}); diff --git a/webpack.conf.js b/webpack.conf.js index c12f3a1c95a..c0fd9187a5e 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -5,9 +5,10 @@ var webpack = require('webpack'); var helpers = require('./gulpHelpers.js'); var { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); var argv = require('yargs').argv; +const babelConfig = require('./babelConfig.js')({disableFeatures: helpers.getDisabledFeatures()}); var plugins = [ - new webpack.EnvironmentPlugin({'LiveConnectMode': null}) + new webpack.EnvironmentPlugin({'LiveConnectMode': null}), ]; if (argv.analyze) { @@ -70,7 +71,7 @@ module.exports = { use: [ { loader: 'babel-loader', - options: helpers.getAnalyticsOptions(), + options: Object.assign({}, babelConfig, helpers.getAnalyticsOptions()), } ] }, @@ -80,6 +81,7 @@ module.exports = { use: [ { loader: 'babel-loader', + options: babelConfig } ], }