diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 174fa6ffe6e..427f775c44b 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -212,9 +212,11 @@ export function requestBidHook(next, config) { // into bid requests later. if (adUnits && pubcid) { adUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - Object.assign(bid, {crumbs: {pubcid}}); - }); + if (unit.bids && utils.isArray(unit.bids)) { + unit.bids.forEach((bid) => { + Object.assign(bid, {crumbs: {pubcid}}); + }); + } }); } diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 4df537e0eb3..ffd242e57ac 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -66,7 +66,7 @@ export function isUsingNewSizeMapping(adUnits) { }); // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids.forEach(bidder => { + adUnit.bids && utils.isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { if (bidder.sizeConfig) { if (isUsingSizeMappingBool === false) { isUsingSizeMappingBool = true; @@ -168,8 +168,15 @@ export function checkAdUnitSetupHook(adUnits) { } const validatedAdUnits = []; adUnits.forEach(adUnit => { + const bids = adUnit.bids; const mediaTypes = adUnit.mediaTypes; let validatedBanner, validatedVideo, validatedNative; + + if (!bids || !utils.isArray(bids)) { + utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + return; + } + if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; diff --git a/modules/userId/index.js b/modules/userId/index.js index 14f7ad3599b..9eaa2c4c369 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -109,14 +109,14 @@ */ import find from 'core-js-pure/features/array/find.js'; -import {config} from '../../src/config.js'; +import { config } from '../../src/config.js'; import events from '../../src/events.js'; import * as utils from '../../src/utils.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; -import {gdprDataHandler} from '../../src/adapterManager.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; +import { gdprDataHandler } from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; -import {module, hook} from '../../src/hook.js'; -import {createEidsArray} from './eids.js'; +import { module, hook } from '../../src/hook.js'; +import { createEidsArray } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; @@ -313,8 +313,8 @@ function hasGDPRConsent(consentData) { * @param {function} cb - callback for after processing is done. */ function processSubmoduleCallbacks(submodules, cb) { - const done = cb ? utils.delayExecution(cb, submodules.length) : function() { }; - submodules.forEach(function(submodule) { + const done = cb ? utils.delayExecution(cb, submodules.length) : function () { }; + submodules.forEach(function (submodule) { submodule.callback(function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { @@ -365,11 +365,13 @@ function addIdDataToAdUnitBids(adUnits, submodules) { const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); if (Object.keys(combinedSubmoduleIds).length) { adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - // create a User ID object on the bid, - bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = combinedSubmoduleIdsAsEids; - }); + if (adUnit.bids && utils.isArray(adUnit.bids)) { + adUnit.bids.forEach(bid => { + // create a User ID object on the bid, + bid.userId = combinedSubmoduleIds; + bid.userIdAsEids = combinedSubmoduleIdsAsEids; + }); + } }); } } @@ -392,7 +394,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // delay auction until ids are available delayed = true; let continued = false; - const continueCallback = function() { + const continueCallback = function () { if (!continued) { continued = true; continueAuction(); @@ -409,7 +411,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout if (syncDelay > 0) { - setTimeout(function() { + setTimeout(function () { processSubmoduleCallbacks(submodulesWithCallbacks); }, syncDelay); } else { @@ -437,7 +439,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { */ export function requestBidsHook(fn, reqBidsConfigObj) { // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(function() { + initializeSubmodulesAndExecuteCallbacks(function () { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); // calling fn allows prebid to continue processing @@ -469,7 +471,7 @@ function getUserIdsAsEids() { * This hook returns updated list of submodules which are allowed to do get user id based on TCF 2 enforcement rules configured */ export const validateGdprEnforcement = hook('sync', function (submodules, consentData) { - return {userIdModules: submodules, hasValidated: consentData && consentData.hasValidated}; + return { userIdModules: submodules, hasValidated: consentData && consentData.hasValidated }; }, 'validateGdprEnforcement'); /** @@ -483,7 +485,7 @@ function initSubmodules(submodules, consentData) { setStoredConsentData(consentData); // gdpr consent with purpose one is required, otherwise exit immediately - let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return []; diff --git a/src/prebid.js b/src/prebid.js index 31e0140cfe2..58c4feb4118 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -149,7 +149,14 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + const bids = adUnit.bids; let validatedBanner, validatedVideo, validatedNative; + + if (!bids || !utils.isArray(bids)) { + utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + return; + } + if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 77e17ef360f..ab06ddc8f51 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -205,6 +205,18 @@ describe('sizeMappingV2', function () { expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); }); + it('should filter out adUnit if it does not contain the required property "bids"', function() { + let adUnits = utils.deepClone(AD_UNITS); + delete adUnits[0].mediaTypes; + // before checkAdUnitSetupHook is called, the length of the adUnits should equal '2' + expect(adUnits.length).to.equal(2); + adUnits = checkAdUnitSetupHook(adUnits); + + // after checkAdUnitSetupHook is called, the length of the adUnits should be '1' + expect(adUnits.length).to.equal(1); + expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); + }); + it('should filter out adUnit if it has declared property mediaTypes with an empty object', function () { let adUnits = utils.deepClone(AD_UNITS); adUnits[0].mediaTypes = {}; @@ -657,7 +669,7 @@ describe('sizeMappingV2', function () { }, native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); @@ -679,7 +691,7 @@ describe('sizeMappingV2', function () { }, native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); @@ -698,7 +710,7 @@ describe('sizeMappingV2', function () { mediaTypes: { native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 960ccf08c92..24ca3ecbda5 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1765,6 +1765,32 @@ describe('Unit: Prebid Module', function () { 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 () { + const adUnits = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [300, 400] + } + }, + bids: [{code: 'appnexus', params: 1234}] + }, { + code: 'bad-ad-unit-2', + mediaTypes: { + banner: { + sizes: [300, 400] + } + } + }]; + + $$PREBID_GLOBAL$$.requestBids({ + adUnits: adUnits + }); + expect(auctionArgs.adUnits.length).to.equal(1); + expect(auctionArgs.adUnits[1]).to.not.exist; + assert.ok(logErrorSpy.calledWith("Detected adUnit.code 'bad-ad-unit-2' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.")); + }); }); }); });