diff --git a/integrationExamples/gpt/responsiveAds_sizeMappingV2.html b/integrationExamples/gpt/responsiveAds_sizeMappingV2.html
new file mode 100644
index 00000000000..d262af5199a
--- /dev/null
+++ b/integrationExamples/gpt/responsiveAds_sizeMappingV2.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prebid.js Test
+ Div-1
+
+
+
+
+
+
diff --git a/modules/adpod.js b/modules/adpod.js
index 46f7f80d47e..a84f4f16285 100644
--- a/modules/adpod.js
+++ b/modules/adpod.js
@@ -271,7 +271,7 @@ export function checkAdUnitSetupHook(fn, adUnits) {
let errMsg = `Detected missing or incorrectly setup fields for an adpod adUnit. Please review the following fields of adUnitCode: ${adUnit.code}. This adUnit will be removed from the auction.`;
- let playerSize = !!(videoConfig.playerSize && utils.isArrayOfNums(videoConfig.playerSize));
+ let playerSize = !!((videoConfig.playerSize && utils.isArrayOfNums(videoConfig.playerSize)) || (videoConfig.sizeConfig));
let adPodDurationSec = !!(videoConfig.adPodDurationSec && utils.isNumber(videoConfig.adPodDurationSec) && videoConfig.adPodDurationSec > 0);
let durationRangeSec = !!(videoConfig.durationRangeSec && utils.isArrayOfNums(videoConfig.durationRangeSec) && videoConfig.durationRangeSec.every(range => range > 0));
diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js
new file mode 100644
index 00000000000..ec3604faa97
--- /dev/null
+++ b/modules/sizeMappingV2.js
@@ -0,0 +1,541 @@
+/**
+ * This modules adds support for the new Size Mapping spec described here. https://github.com/prebid/Prebid.js/issues/4129
+ * This implementation replaces global sizeConfig with a adUnit/bidder level sizeConfig with support for labels.
+ */
+
+import * as utils from '../src/utils.js';
+import { processNativeAdUnitParams } from '../src/native.js';
+import { adunitCounter } from '../src/adUnits.js';
+import includes from 'core-js/library/fn/array/includes.js';
+import { getHook } from '../src/hook.js';
+import {
+ adUnitSetupChecks
+} from '../src/prebid.js';
+
+// allows for sinon.spy, sinon.stub, etc to unit test calls made to these functions internally
+export const internal = {
+ checkBidderSizeConfigFormat,
+ getActiveSizeBucket,
+ getFilteredMediaTypes,
+ getAdUnitDetail,
+ getRelevantMediaTypesForBidder,
+ isLabelActivated
+};
+
+// 'sizeMappingInternalStore' contains information whether a particular auction is using size mapping V2 (the new size mapping spec),
+// and it also contains additional information on each adUnit, as such, mediaTypes, activeViewport, etc.
+// This information is required by the 'getBids' function.
+export const sizeMappingInternalStore = createSizeMappingInternalStore();
+
+function createSizeMappingInternalStore() {
+ const sizeMappingInternalStore = {};
+
+ return {
+ initializeStore: function(auctionId, isUsingSizeMappingBool) {
+ sizeMappingInternalStore[auctionId] = {
+ usingSizeMappingV2: isUsingSizeMappingBool,
+ adUnits: []
+ };
+ },
+ getAuctionDetail: function(auctionId) {
+ return sizeMappingInternalStore[auctionId];
+ },
+ setAuctionDetail: function(auctionId, adUnitDetail) {
+ sizeMappingInternalStore[auctionId].adUnits.push(adUnitDetail);
+ }
+ }
+}
+
+// returns "true" if atleast one of the adUnit in the adUnits array has declared a Ad Unit or(and) Bidder level sizeConfig
+// returns "false" otherwise
+export function isUsingNewSizeMapping(adUnits) {
+ let isUsingSizeMappingBool = false;
+ adUnits.forEach(adUnit => {
+ if (adUnit.mediaTypes) {
+ // checks for the presence of sizeConfig property at the adUnit.mediaTypes object
+ Object.keys(adUnit.mediaTypes).forEach(mediaType => {
+ if (adUnit.mediaTypes[mediaType].sizeConfig) {
+ if (isUsingSizeMappingBool === false) {
+ isUsingSizeMappingBool = true;
+ }
+ }
+ });
+
+ // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object
+ adUnit.bids.forEach(bidder => {
+ if (bidder.sizeConfig) {
+ if (isUsingSizeMappingBool === false) {
+ isUsingSizeMappingBool = true;
+ }
+ }
+ });
+ }
+ });
+ return isUsingSizeMappingBool;
+}
+
+// returns "adUnits" array which have passed sizeConfig validation checks in addition to mediaTypes checks
+// deletes properties from adUnit which fail validation.
+export function checkAdUnitSetupHook(adUnits) {
+ return adUnits.filter(adUnit => {
+ const mediaTypes = adUnit.mediaTypes;
+ 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 false;
+ }
+
+ if (mediaTypes.banner) {
+ const banner = mediaTypes.banner;
+ if (banner.sizes) {
+ adUnitSetupChecks.validateBannerMediaType(adUnit);
+ } else if (banner.sizeConfig) {
+ if (Array.isArray(banner.sizeConfig)) {
+ let deleteBannerMediaType = false;
+ banner.sizeConfig.forEach((config, index) => {
+ // verify if all config objects include "minViewPort" and "sizes" property.
+ // if not, remove the mediaTypes.banner object
+ const keys = Object.keys(config);
+ if (!(includes(keys, 'minViewPort') && includes(keys, 'sizes'))) {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] is missing required property minViewPort or sizes or both.`);
+ deleteBannerMediaType = true;
+ return;
+ }
+
+ // check if the config.sizes property is in [w, h] format, if yes, change it to [[w, h]] format.
+ const bannerSizes = adUnitSetupChecks.validateSizes(config.sizes);
+ if (utils.isArrayOfNums(config.minViewPort, 2)) {
+ if (config.sizes.length > 0 && bannerSizes.length > 0) {
+ config.sizes = bannerSizes;
+ } else if (config.sizes.length === 0) {
+ // If a size bucket doesn't have any sizes, sizes is an empty array, i.e. sizes: []. This check takes care of that.
+ config.sizes = [config.sizes];
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`);
+ deleteBannerMediaType = true;
+ }
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`);
+ deleteBannerMediaType = true;
+ }
+ });
+ if (deleteBannerMediaType) {
+ utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.banner has been removed due to error in sizeConfig.`);
+ delete adUnit.mediaTypes.banner;
+ }
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`);
+ delete adUnit.mediaTypes.banner;
+ }
+ } else {
+ utils.logError('Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.');
+ delete adUnit.mediaTypes.banner;
+ }
+ }
+
+ if (mediaTypes.video) {
+ const video = mediaTypes.video;
+ if (video.playerSize) {
+ adUnitSetupChecks.validateVideoMediaType(adUnit);
+ } else if (video.sizeConfig) {
+ if (Array.isArray(video.sizeConfig)) {
+ let deleteVideoMediaType = false;
+ video.sizeConfig.forEach((config, index) => {
+ // verify if all config objects include "minViewPort" and "playerSize" property.
+ // if not, remove the mediaTypes.video object
+ const keys = Object.keys(config);
+ if (!(includes(keys, 'minViewPort') && includes(keys, 'playerSize'))) {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`);
+ deleteVideoMediaType = true;
+ return;
+ }
+ // check if the config.playerSize property is in [w, h] format, if yes, change it to [[w, h]] format.
+ let tarPlayerSizeLen = (typeof config.playerSize[0] === 'number') ? 2 : 1;
+ const videoSizes = adUnitSetupChecks.validateSizes(config.playerSize, tarPlayerSizeLen);
+ if (utils.isArrayOfNums(config.minViewPort, 2)) {
+ if (tarPlayerSizeLen === 2) {
+ utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.');
+ }
+ if (config.playerSize.length > 0 && videoSizes.length > 0) {
+ config.playerSize = videoSizes;
+ } else if (config.playerSize.length === 0) {
+ // If a size bucket doesn't have any playerSize, playerSize is an empty array, i.e. playerSize: []. This check takes care of that.
+ config.playerSize = [config.playerSize];
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`);
+ deleteVideoMediaType = true;
+ }
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`);
+ deleteVideoMediaType = true;
+ }
+ });
+ if (deleteVideoMediaType) {
+ utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig has been removed due to error in sizeConfig.`);
+ delete adUnit.mediaTypes.video.sizeConfig;
+ }
+ } else {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`);
+ return delete adUnit.mediaTypes.video.sizeConfig;
+ }
+ }
+ }
+
+ if (mediaTypes.native) {
+ const native = mediaTypes.native;
+ adUnitSetupChecks.validateNativeMediaType(adUnit);
+
+ if (mediaTypes.native.sizeConfig) {
+ native.sizeConfig.forEach(config => {
+ // verify if all config objects include "minViewPort" and "active" property.
+ // if not, remove the mediaTypes.native object
+ const keys = Object.keys(config);
+ if (!(includes(keys, 'minViewPort') && includes(keys, 'active'))) {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`);
+ return delete adUnit.mediaTypes.native.sizeConfig;
+ }
+
+ if (!(utils.isArrayOfNums(config.minViewPort, 2) && typeof config.active === 'boolean')) {
+ utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`);
+ return delete adUnit.mediaTypes.native.sizeConfig;
+ }
+ });
+ }
+ }
+
+ return true;
+ });
+}
+
+getHook('checkAdUnitSetup').before(function (fn, adUnits) {
+ const usingNewSizeMapping = isUsingNewSizeMapping(adUnits);
+ if (usingNewSizeMapping) {
+ // if adUnits are found using the sizeMappingV2 spec, we run additional checks on them for checking the validity of sizeConfig object
+ // in addition to running the base checks on the mediaType object and return the adUnit without calling the base function.
+ adUnits = checkAdUnitSetupHook(adUnits);
+ return fn.bail(adUnits);
+ } else {
+ // if presence of sizeMappingV2 spec is not detected on adUnits, we default back to the original checks defined in the base function.
+ return fn.call(this, adUnits);
+ }
+});
+
+// checks if the sizeConfig object declared at the Bidder level is in the right format or not.
+export function checkBidderSizeConfigFormat(sizeConfig) {
+ let didCheckPass = true;
+ if (Array.isArray(sizeConfig) && sizeConfig.length > 0) {
+ sizeConfig.forEach(config => {
+ const keys = Object.keys(config);
+ if ((includes(keys, 'minViewPort') &&
+ includes(keys, 'relevantMediaTypes')) &&
+ utils.isArrayOfNums(config.minViewPort, 2) &&
+ Array.isArray(config.relevantMediaTypes) &&
+ config.relevantMediaTypes.length > 0 &&
+ (config.relevantMediaTypes.length > 1 ? (config.relevantMediaTypes.every(mt => (includes(['banner', 'video', 'native'], mt))))
+ : (['none', 'banner', 'video', 'native'].indexOf(config.relevantMediaTypes[0] > -1)))) {
+ didCheckPass = didCheckPass && true;
+ } else {
+ didCheckPass = false;
+ }
+ });
+ } else {
+ didCheckPass = false;
+ }
+ return didCheckPass;
+}
+
+getHook('getBids').before(function (fn, bidderInfo) {
+ // check if the adUnit is using sizeMappingV2 specs and store the result in _sizeMappingUsageMap.
+ if (typeof sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId) === 'undefined') {
+ const isUsingSizeMappingBool = isUsingNewSizeMapping(bidderInfo.adUnits);
+
+ // initialize sizeMappingInternalStore for the first time for a particular auction
+ sizeMappingInternalStore.initializeStore(bidderInfo.auctionId, isUsingSizeMappingBool);
+ }
+ if (sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId).usingSizeMappingV2) {
+ // if adUnit is found using sizeMappingV2 specs, run the getBids function which processes the sizeConfig object
+ // and returns the bids array for a particular bidder.
+ const bids = getBids(bidderInfo);
+ return fn.bail(bids);
+ } else {
+ // if not using sizeMappingV2, default back to the getBids function defined in adapterManager.
+ return fn.call(this, bidderInfo);
+ }
+});
+
+/**
+ * Given an Ad Unit or a Bid as an input, returns a boolean telling if the Ad Unit/ Bid is active based on label checks
+ * @param {Object} bidOrAdUnit Either the Ad Unit object or the Bid object
+ * @param {Array} activeLabels List of active labels passed as an argument to pbjs.requestBids function
+ * @param {string} adUnitCode Unique string identifier for an Ad Unit.
+ * @returns {boolean} Represents if the Ad Unit or the Bid is active or not
+ */
+export function isLabelActivated(bidOrAdUnit, activeLabels, adUnitCode) {
+ let labelOperator;
+ const labelsFound = Object.keys(bidOrAdUnit).filter(prop => prop === 'labelAny' || prop === 'labelAll');
+ if (labelsFound && labelsFound.length > 1) {
+ utils.logWarn(`SizeMappingV2:: ${(bidOrAdUnit.code)
+ ? (`Ad Unit: ${bidOrAdUnit.code} has multiple label operators. Using the first declared operator: ${labelsFound[0]}`)
+ : (`Bidder: ${bidOrAdUnit.bidder} in Ad Unit: ${adUnitCode} has multiple label operators. Using the first declared operator: ${labelsFound[0]}`)}`);
+ }
+ labelOperator = labelsFound[0];
+
+ if (labelOperator === 'labelAll' && Array.isArray(bidOrAdUnit[labelOperator])) {
+ if (bidOrAdUnit.labelAll.length === 0) {
+ utils.logWarn(`SizeMappingV2:: Ad Unit: ${bidOrAdUnit.code} has declared property labelAll with an empty array. Ad Unit is still enabled!`);
+ return true;
+ }
+ return bidOrAdUnit.labelAll.every(label => includes(activeLabels, label));
+ } else if (labelOperator === 'labelAny' && Array.isArray(bidOrAdUnit[labelOperator])) {
+ if (bidOrAdUnit.labelAny.length === 0) {
+ utils.logWarn(`SizeMappingV2:: Ad Unit: ${bidOrAdUnit.code} has declared property labelAny with an empty array. Ad Unit is still enabled!`);
+ return true;
+ }
+ return bidOrAdUnit.labelAny.some(label => includes(activeLabels, label));
+ }
+ return true;
+}
+
+/**
+ * Processes the MediaTypes object and calculates the active size buckets for each Media Type. Uses `window.innerWidth` and `window.innerHeight`
+ * to calculate the width and height of the active Viewport.
+ * @param {MediaTypes} mediaTypes Contains information about supported media types for an Ad Unit and size information for each of those types
+ * @returns {FilteredMediaTypes} Filtered mediaTypes object with relevant media types filtered by size buckets based on activeViewPort size
+ */
+export function getFilteredMediaTypes(mediaTypes) {
+ let
+ activeViewportWidth,
+ activeViewportHeight,
+ transformedMediaTypes;
+
+ transformedMediaTypes = utils.deepClone(mediaTypes);
+
+ let activeSizeBucket = {
+ banner: undefined,
+ video: undefined,
+ native: undefined
+ }
+
+ try {
+ activeViewportWidth = utils.getWindowTop().innerWidth;
+ activeViewportHeight = utils.getWindowTop().innerHeight;
+ } catch (e) {
+ utils.logWarn(`SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`);
+ activeViewportWidth = window.innerWidth;
+ activeViewportHeight = window.innerHeight;
+ }
+ const activeViewport = [activeViewportWidth, activeViewportHeight];
+ Object.keys(mediaTypes).map(mediaType => {
+ const sizeConfig = mediaTypes[mediaType].sizeConfig;
+ if (sizeConfig) {
+ activeSizeBucket[mediaType] = getActiveSizeBucket(sizeConfig, activeViewport);
+ const filteredSizeConfig = sizeConfig.filter(config => config.minViewPort === activeSizeBucket[mediaType] && isSizeConfigActivated(mediaType, config));
+ transformedMediaTypes[mediaType] = Object.assign({ filteredSizeConfig }, mediaTypes[mediaType]);
+
+ // transform mediaTypes object
+ const config = {
+ banner: 'sizes',
+ video: 'playerSize'
+ };
+
+ if (transformedMediaTypes[mediaType].filteredSizeConfig.length > 0) {
+ // map sizes or playerSize property in filteredSizeConfig object to transformedMediaTypes.banner.sizes if mediaType is banner
+ // or transformedMediaTypes.video.playerSize if the mediaType in video.
+ // doesn't apply to native mediaType since native doesn't have any property defining 'sizes' or 'playerSize'.
+ if (mediaType !== 'native') {
+ transformedMediaTypes[mediaType][config[mediaType]] = transformedMediaTypes[mediaType].filteredSizeConfig[0][config[mediaType]];
+ }
+ } else {
+ delete transformedMediaTypes[mediaType];
+ }
+ }
+ })
+
+ // filter out 'undefined' values from activeSizeBucket object and attach sizes/playerSize information against the active size bucket.
+ const sizeBucketToSizeMap = Object
+ .keys(activeSizeBucket)
+ .filter(mediaType => activeSizeBucket[mediaType] !== undefined)
+ .reduce((sizeBucketToSizeMap, mediaType) => {
+ sizeBucketToSizeMap[mediaType] = {
+ activeSizeBucket: activeSizeBucket[mediaType],
+ activeSizeDimensions: (mediaType === 'banner') ? (
+ // banner mediaType gets deleted incase no sizes are specified for a given size bucket, that's why this check is necessary
+ (transformedMediaTypes.banner) ? (transformedMediaTypes.banner.sizes) : ([])
+ ) : ((mediaType === 'video') ? (
+ // video mediaType gets deleted incase no playerSize is specified for a given size bucket, that's why this check is necessary
+ (transformedMediaTypes.video) ? (transformedMediaTypes.video.playerSize) : ([])
+ ) : ('NA'))
+ };
+ return sizeBucketToSizeMap;
+ }, {});
+
+ return { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes };
+};
+
+/**
+ * Evaluates the given sizeConfig object and checks for various properties to determine if the sizeConfig is active or not. For example,
+ * let's suppose the sizeConfig is for a Banner media type. Then, if the sizes property is found empty, it returns false, else returns true.
+ * In case of a Video media type, it checks the playerSize property. If found empty, returns false, else returns true.
+ * In case of a Native media type, it checks the active property. If found false, returns false, if found true, returns true.
+ * @param {string} mediaType It can be 'banner', 'native' or 'video'
+ * @param {Object} sizeConfig Represents the sizeConfig object which is active based on the current viewport size
+ * @returns {boolean} Represents if the size config is active or not
+ */
+export function isSizeConfigActivated(mediaType, sizeConfig) {
+ switch (mediaType) {
+ case 'banner':
+ // we need this check, sizeConfig.sizes[0].length > 0, in place because a sizeBucket can have sizes: [],
+ // gets converted to sizes: [[]] in the checkAdUnitSetupHook function
+ return sizeConfig.sizes && sizeConfig.sizes.length > 0 && sizeConfig.sizes[0].length > 0;
+ case 'video':
+ // for why we need the last check, read the above comment
+ return sizeConfig.playerSize && sizeConfig.playerSize.length > 0 && sizeConfig.playerSize[0].length > 0;
+ case 'native':
+ return sizeConfig.active;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Returns the active size bucket for a given media type
+ * @param {Array} sizeConfig SizeConfig defines the characteristics of an Ad Unit categorised into multiple size buckets per media type
+ * @param {Array} activeViewport Viewport size of the browser in the form [w, h] (w -> width, h -> height)
+ * Calculated at the time of making call to pbjs.requestBids function
+ * @returns {Array} The active size bucket matching the activeViewPort, for example: [750, 0]
+ */
+export function getActiveSizeBucket(sizeConfig, activeViewport) {
+ let activeSizeBucket = [];
+ sizeConfig
+ .sort((a, b) => a.minViewPort[0] - b.minViewPort[0])
+ .forEach(config => {
+ if (activeViewport[0] >= config.minViewPort[0]) {
+ if (activeViewport[1] >= config.minViewPort[1]) {
+ activeSizeBucket = config.minViewPort;
+ } else {
+ activeSizeBucket = [];
+ }
+ }
+ })
+ return activeSizeBucket;
+}
+
+export function getRelevantMediaTypesForBidder(sizeConfig, activeViewport) {
+ if (internal.checkBidderSizeConfigFormat(sizeConfig)) {
+ const activeSizeBucket = internal.getActiveSizeBucket(sizeConfig, activeViewport);
+ return sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes'];
+ }
+ return [];
+}
+
+// sets sizeMappingInternalStore for a given auctionId with relevant adUnit information returned from the call to 'getFilteredMediaTypes' function
+// returns adUnit details object.
+export function getAdUnitDetail(auctionId, adUnit) {
+ const adUnitDetail = sizeMappingInternalStore.getAuctionDetail(auctionId).adUnits.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code);
+
+ if (adUnitDetail.length > 0) {
+ return adUnitDetail[0];
+ } else {
+ const { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = internal.getFilteredMediaTypes(adUnit.mediaTypes);
+
+ const adUnitDetail = {
+ adUnitCode: adUnit.code,
+ mediaTypes,
+ sizeBucketToSizeMap,
+ activeViewport,
+ transformedMediaTypes
+ };
+
+ // 'filteredMediaTypes' are the mediaTypes that got removed/filtered-out from adUnit.mediaTypes after sizeConfig filtration.
+ const filteredMediaTypes = Object.keys(mediaTypes).filter(mt => Object.keys(transformedMediaTypes).indexOf(mt) === -1)
+
+ utils.logInfo(`SizeMappingV2:: AdUnit: ${adUnit.code} - Active size buckets after filtration: `, sizeBucketToSizeMap);
+ if (filteredMediaTypes.length > 0) {
+ utils.logInfo(`SizeMappingV2:: AdUnit: ${adUnit.code} - mediaTypes that got filtered out: ${filteredMediaTypes}`);
+ }
+
+ // set adUnitDetail in sizeMappingInternalStore against the correct 'auctionId'.
+ sizeMappingInternalStore.setAuctionDetail(auctionId, adUnitDetail);
+ return adUnitDetail;
+ }
+}
+
+export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src }) {
+ return adUnits.reduce((result, adUnit) => {
+ if (internal.isLabelActivated(adUnit, labels, adUnit.code)) {
+ if (adUnit.mediaTypes && utils.isValidMediaTypes(adUnit.mediaTypes)) {
+ const { activeViewport, transformedMediaTypes } = internal.getAdUnitDetail(auctionId, adUnit);
+
+ // check if adUnit has any active media types remaining, if not drop the adUnit from auction,
+ // else proceed to evaluate the bids object.
+ if (Object.keys(transformedMediaTypes).length === 0) {
+ utils.logInfo(`SizeMappingV2:: Ad Unit: ${adUnit.code} is disabled since there are no active media types after sizeConfig filtration.`);
+ return result;
+ }
+ result
+ .push(adUnit.bids.filter(bid => bid.bidder === bidderCode)
+ .reduce((bids, bid) => {
+ if (internal.isLabelActivated(bid, labels, adUnit.code)) {
+ // handle native params
+ const nativeParams = adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native');
+ if (nativeParams) {
+ bid = Object.assign({}, bid, {
+ nativeParams: processNativeAdUnitParams(nativeParams)
+ });
+ }
+
+ bid = Object.assign({}, bid, utils.getDefinedParams(adUnit, ['mediaType', 'renderer']));
+
+ if (bid.sizeConfig) {
+ const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport);
+ if (relevantMediaTypes.length === 0) {
+ utils.logError(`SizeMappingV2:: AdUnit: ${adUnit.code}, Bidder: ${bidderCode} - sizeConfig is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`);
+ bid = Object.assign({}, bid);
+ } else if (relevantMediaTypes[0] !== 'none') {
+ const bidderMediaTypes = Object
+ .keys(transformedMediaTypes)
+ .filter(mt => relevantMediaTypes.indexOf(mt) > -1)
+ .reduce((mediaTypes, mediaType) => {
+ mediaTypes[mediaType] = transformedMediaTypes[mediaType];
+ return mediaTypes;
+ }, {});
+
+ if (Object.keys(bidderMediaTypes).length > 0) {
+ bid = Object.assign({}, bid, { mediaTypes: bidderMediaTypes });
+ } else {
+ utils.logInfo(`SizeMappingV2:: AdUnit: ${adUnit.code}, Bidder: ${bid.bidder} - 'relevantMediaTypes' for this bidder does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`);
+ return bids;
+ }
+ } else {
+ utils.logInfo(`SizeMappingV2:: AdUnit: ${adUnit.code}, Bidder: ${bid.bidder} - 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`);
+ return bids;
+ }
+ }
+ bids.push(Object.assign({}, bid, {
+ adUnitCode: adUnit.code,
+ transactionId: adUnit.transactionId,
+ sizes: utils.deepAccess(transformedMediaTypes, 'banner.sizes') || utils.deepAccess(transformedMediaTypes, 'video.playerSize') || [],
+ mediaTypes: bid.mediaTypes || transformedMediaTypes,
+ bidId: bid.bid_id || utils.getUniqueIdentifierStr(),
+ bidderRequestId,
+ auctionId,
+ src,
+ bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code),
+ bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder),
+ bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder)
+ }));
+ return bids;
+ } else {
+ utils.logInfo(`SizeMappingV2:: AdUnit: ${adUnit.code}, Bidder: ${bid.bidder} - Label check for this bidder has failed. This bidder is disabled.`);
+ return bids;
+ }
+ }, []));
+ } else {
+ utils.logWarn(`SizeMappingV2:: Ad Unit: ${adUnit.code} has declared invalid mediaTypes or has not declared a mediaTypes property`);
+ }
+ } else {
+ utils.logInfo(`SizeMappingV2:: Ad Unit: ${adUnit.code} is disabled due to failing label check.`);
+ return result;
+ }
+ return result;
+ }, []).reduce(utils.flatten, []).filter(val => val !== '');
+}
diff --git a/src/adapterManager.js b/src/adapterManager.js
index 134c21e5d7c..9886934ab47 100644
--- a/src/adapterManager.js
+++ b/src/adapterManager.js
@@ -116,6 +116,8 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src})
}, []).reduce(flatten, []).filter(val => val !== '');
}
+const hookedGetBids = hook('sync', getBids, 'getBids');
+
function getAdUnitCopyForPrebidServer(adUnits) {
let adaptersServerSide = _s2sConfig.bidders;
let adUnitsCopy = utils.deepClone(adUnits);
@@ -229,7 +231,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
auctionId,
bidderRequestId,
tid,
- bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}),
+ bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}),
auctionStart: auctionStart,
timeout: _s2sConfig.timeout,
src: CONSTANTS.S2S.SRC,
@@ -264,7 +266,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
bidderCode,
auctionId,
bidderRequestId,
- bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsClientCopy), labels, src: 'client'}),
+ bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsClientCopy), labels, src: 'client'}),
auctionStart: auctionStart,
timeout: cbTimeout,
refererInfo
diff --git a/src/prebid.js b/src/prebid.js
index e9fd0b299a8..aa2d2493e7b 100644
--- a/src/prebid.js
+++ b/src/prebid.js
@@ -68,20 +68,74 @@ function setRenderSize(doc, width, height) {
}
}
-export const checkAdUnitSetup = hook('sync', function (adUnits) {
- function validateSizes(sizes, targLength) {
- let cleanSizes = [];
- if (utils.isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) {
- // check if an array of arrays or array of numbers
- if (sizes.every(sz => isArrayOfNums(sz, 2))) {
- cleanSizes = sizes;
- } else if (isArrayOfNums(sizes, 2)) {
- cleanSizes.push(sizes);
- }
+function validateSizes(sizes, targLength) {
+ let cleanSizes = [];
+ if (utils.isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) {
+ // check if an array of arrays or array of numbers
+ if (sizes.every(sz => isArrayOfNums(sz, 2))) {
+ cleanSizes = sizes;
+ } else if (isArrayOfNums(sizes, 2)) {
+ cleanSizes.push(sizes);
+ }
+ }
+ return cleanSizes;
+}
+
+function validateBannerMediaType(adUnit) {
+ const banner = adUnit.mediaTypes.banner;
+ const bannerSizes = validateSizes(banner.sizes);
+ if (bannerSizes.length > 0) {
+ banner.sizes = bannerSizes;
+ // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes
+ adUnit.sizes = bannerSizes;
+ } else {
+ utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.');
+ delete adUnit.mediaTypes.banner
+ }
+}
+
+function validateVideoMediaType(adUnit) {
+ const video = adUnit.mediaTypes.video;
+ let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1;
+
+ const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen);
+ if (videoSizes.length > 0) {
+ if (tarPlayerSizeLen === 2) {
+ utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.');
}
- return cleanSizes;
+ video.playerSize = videoSizes;
+ // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize
+ adUnit.sizes = videoSizes;
+ } else {
+ utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.');
+ delete adUnit.mediaTypes.video.playerSize;
+ }
+}
+
+function validateNativeMediaType(adUnit) {
+ const native = adUnit.mediaTypes.native;
+ if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) {
+ utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.');
+ delete adUnit.mediaTypes.native.image.sizes;
+ }
+ if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) {
+ utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.');
+ delete adUnit.mediaTypes.native.image.aspect_ratios;
}
+ if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) {
+ utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.');
+ delete adUnit.mediaTypes.native.icon.sizes;
+ }
+}
+export const adUnitSetupChecks = {
+ validateBannerMediaType,
+ validateVideoMediaType,
+ validateNativeMediaType,
+ validateSizes
+};
+
+export const checkAdUnitSetup = hook('sync', function (adUnits) {
return adUnits.filter(adUnit => {
const mediaTypes = adUnit.mediaTypes;
if (!mediaTypes || Object.keys(mediaTypes).length === 0) {
@@ -90,45 +144,18 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) {
}
if (mediaTypes.banner) {
- const bannerSizes = validateSizes(mediaTypes.banner.sizes);
- if (bannerSizes.length > 0) {
- mediaTypes.banner.sizes = bannerSizes;
- // TODO eventually remove this internal copy once we're ready to deprecate bidders from reading this adUnit.sizes property
- adUnit.sizes = bannerSizes;
- } else {
- utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.');
- delete adUnit.mediaTypes.banner;
- }
+ validateBannerMediaType(adUnit);
}
if (mediaTypes.video) {
const video = mediaTypes.video;
if (video.playerSize) {
- let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1;
- const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen);
- if (videoSizes.length > 0) {
- adUnit.sizes = video.playerSize = videoSizes;
- } else {
- utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.');
- delete adUnit.mediaTypes.video.playerSize;
- }
+ validateVideoMediaType(adUnit);
}
}
if (mediaTypes.native) {
- const nativeObj = mediaTypes.native;
- if (nativeObj.image && nativeObj.image.sizes && !Array.isArray(nativeObj.image.sizes)) {
- utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.');
- delete adUnit.mediaTypes.native.image.sizes;
- }
- if (nativeObj.image && nativeObj.image.aspect_ratios && !Array.isArray(nativeObj.image.aspect_ratios)) {
- utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.');
- delete adUnit.mediaTypes.native.image.aspect_ratios;
- }
- if (nativeObj.icon && nativeObj.icon.sizes && !Array.isArray(nativeObj.icon.sizes)) {
- utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.');
- delete adUnit.mediaTypes.native.icon.sizes;
- }
+ validateNativeMediaType(adUnit);
}
return true;
});
diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js
new file mode 100644
index 00000000000..4b76ce2fa1c
--- /dev/null
+++ b/test/spec/modules/sizeMappingV2_spec.js
@@ -0,0 +1,1580 @@
+import { expect } from 'chai';
+import * as utils from '../../../src/utils.js';
+
+import {
+ isUsingNewSizeMapping,
+ checkAdUnitSetupHook,
+ checkBidderSizeConfigFormat,
+ isLabelActivated,
+ isSizeConfigActivated,
+ getActiveSizeBucket,
+ getRelevantMediaTypesForBidder,
+ sizeMappingInternalStore,
+ getAdUnitDetail,
+ getFilteredMediaTypes,
+ getBids,
+ internal
+} from '../../../modules/sizeMappingV2.js';
+
+import { adUnitSetupChecks } from '../../../src/prebid.js';
+
+const AD_UNITS = [{
+ code: 'div-gpt-ad-1460505748561-0',
+ mediaTypes: {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] }, // remove if < 750px
+ { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px
+ { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px
+ { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px
+ ]
+ },
+ video: {
+ context: 'instream',
+ sizeConfig: [
+ { minViewPort: [0, 0], playerSize: [] },
+ { minViewPort: [800, 0], playerSize: [[640, 400]] },
+ { minViewPort: [1200, 0], playerSize: [] }
+ ]
+ },
+ native: {
+ image: {
+ required: true,
+ sizes: [150, 50]
+ },
+ title: {
+ required: true,
+ len: 80
+ },
+ sponsoredBy: {
+ required: true
+ },
+ clickUrl: {
+ required: true
+ },
+ privacyLink: {
+ required: false
+ },
+ body: {
+ required: true
+ },
+ icon: {
+ required: true,
+ sizes: [50, 50]
+ },
+ sizeConfig: [
+ { minViewPort: [0, 0], active: false },
+ { minViewPort: [600, 0], active: true },
+ { minViewPort: [1000, 0], active: false }
+ ]
+ }
+ },
+ bids: [{
+ bidder: 'appnexus',
+ params: {
+ placementId: 13144370
+ }
+ }]
+}, {
+ code: 'div-gpt-ad-1460505748561-1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 600]]
+ },
+ video: {
+ context: 'instream',
+ playerSize: [300, 460]
+ }
+ },
+ bids: [{
+ bidder: 'appnexus',
+ params: {
+ placementId: 13144370
+ }
+ }, {
+ bidder: 'rubicon',
+ params: {
+ accountId: 14062,
+ siteId: 70608,
+ zoneId: 498816
+ },
+ sizeConfig: [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [800, 0], relevantMediaTypes: ['banner'] },
+ { minViewPort: [1600, 0], relevantMediaTypes: ['none'] }
+ ]
+ }]
+}];
+
+// deletes the sizeConfig property from the adUnits either at the Ad Unit level or at the Bids level or at both.
+function deleteSizeConfig(adUnits, config) {
+ const { adUnitLevel, bidsLevel } = config;
+ adUnits.forEach(adUnit => {
+ const mediaTypes = adUnit.mediaTypes;
+ if (adUnitLevel) {
+ Object.keys(mediaTypes).forEach(mediaType => {
+ if (mediaTypes[mediaType].sizeConfig) {
+ delete adUnit.mediaTypes[mediaType].sizeConfig;
+ if (mediaType === 'banner') {
+ adUnit.mediaTypes[mediaType].sizes = [[300, 600], [300, 200]];
+ } else if (mediaType === 'video') {
+ adUnit.mediaTypes[mediaType].playerSize = [[640, 400]];
+ }
+ }
+ });
+ }
+
+ if (bidsLevel) {
+ adUnit.bids.forEach(bid => {
+ if (bid.sizeConfig) {
+ delete bid.sizeConfig;
+ }
+ });
+ }
+ });
+ return adUnits;
+}
+
+describe('sizeMappingV2', function () {
+ describe('isUsingNewSizeMaping(adUntis, auctionId)', function () {
+ it('should return "false" if sizeConfig is not declared both at the adUnits level and the bids level', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+
+ // delete the sizeConfig property from AD_UNITS object at the Ad Unit and the Bids level both.
+ adUnits = deleteSizeConfig(adUnits, { adUnitLevel: true, bidsLevel: true });
+ const usingNewSizeMappingBool = isUsingNewSizeMapping(adUnits);
+
+ // isUsingNewSizeMapping should return false because sizeConfig object is not present both at the Ad Units and the Bids level
+ // for all the adUnits that are checked.
+ expect(usingNewSizeMappingBool).to.be.false;
+ });
+
+ it('should return "true" if sizeConfig is declared at the adUnits level but not at the bids level', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+
+ // delete the sizeConfig property from AD_UNITS object at the Bids level but not at the Ad Unit level.
+ adUnits = deleteSizeConfig(adUnits, { adUnitLevel: false, bidsLevel: true });
+ const usingNewSizeMappingBool = isUsingNewSizeMapping(adUnits);
+
+ // isUsingNewSizeMapping should return true because sizeConfig object is present at the Ad Units level but not at the
+ // Bids level.
+ expect(usingNewSizeMappingBool).to.be.true;
+ });
+
+ it('should return "true" if sizeConfig is declared at the bids level but not at the adUnits level', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+
+ // delete the sizeConfig property from AD_UNITS object at the Ad Unit level but not at the Bids level.
+ adUnits = deleteSizeConfig(adUnits, { adUnitLevel: true, bidsLevel: false });
+ const usingNewSizeMappingBool = isUsingNewSizeMapping(adUnits);
+
+ // isUsingNewSizeMapping should return true because sizeConfig object is present at the Bids level but not at the
+ // Ad Unit level.
+ expect(usingNewSizeMappingBool).to.be.true;
+ });
+
+ it('should return "true" if sizeConfig is declared both at the adUnits level and at the bids level', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+
+ const usingNewSizeMappingBool = isUsingNewSizeMapping(adUnits);
+
+ // isUsingNewSizeMapping should return true because sizeConfig object is present both at the Ad Unit level and at the
+ // Bids level.
+ expect(usingNewSizeMappingBool).to.be.true;
+ });
+ });
+
+ describe('checkAdUnitSetupHook(adUnits)', function () {
+ beforeEach(function () {
+ sinon.spy(utils, 'logError');
+ });
+
+ afterEach(function () {
+ utils.logError.restore();
+ });
+
+ it('should filter out adUnit if it does not contain the required property mediaTypes', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+ delete adUnits[0].mediaTypes;
+ // before checkAdUnitSetupHook is called, the length of adUnits should be '2'
+ expect(adUnits.length).to.equal(2);
+ adUnits = checkAdUnitSetupHook(adUnits);
+
+ // after checkAdUnitSetupHook is called, the length of 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 = {};
+ // before checkAdUnitSetupHook is called, the length of adUnits should be '2'
+ expect(adUnits.length).to.equal(2);
+ adUnits = checkAdUnitSetupHook(adUnits);
+
+ // after checkAdUnitSetupHook is called, the length of adUnits should be '1'
+ expect(adUnits.length).to.equal(1);
+ expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1');
+ });
+
+ it('should log an error message if Ad Unit does not contain the required property "mediaTypes"', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+ delete adUnits[0].mediaTypes;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, 'Detected adUnit.code \'div-gpt-ad-1460505748561-0\' did not have a \'mediaTypes\' object defined. This is a required field for the auction, so this adUnit has been removed.');
+ });
+
+ describe('banner mediaTypes checks', function () {
+ beforeEach(function () {
+ sinon.spy(adUnitSetupChecks, 'validateBannerMediaType');
+ });
+
+ afterEach(function () {
+ adUnitSetupChecks.validateBannerMediaType.restore();
+ });
+
+ it('should delete banner mediaType if it does not constain sizes or sizeConfig property', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+ delete adUnits[0].mediaTypes.banner.sizeConfig;
+
+ // before checkAdUnitSetupHook is called, adUnits[0].mediaTypes.banner property should exist.
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+
+ adUnits = checkAdUnitSetupHook(adUnits);
+
+ // after checkAdUnitSetupHook is called, adUnits[0].mediaTypes.banner property should not exist.
+ expect(adUnits[0].mediaTypes).to.not.have.property('banner');
+ });
+
+ it('should log an error message if mediaTypes.banner does not contain "sizes" or "sizeConfig" property', function () {
+ let adUnits = utils.deepClone(AD_UNITS);
+ // deleteing the sizeConfig property from the first ad unit.
+ delete adUnits[0].mediaTypes.banner.sizeConfig;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, 'Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.');
+ });
+
+ it('should call function "validateBannerMediaType" if mediaTypes.sizes is present', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ checkAdUnitSetupHook(adUnits);
+
+ // since the second Ad Unit in AD_UNITS array uses mediaTypes.sizes, it should get called only once.
+ sinon.assert.callCount(adUnitSetupChecks.validateBannerMediaType, 1);
+ sinon.assert.calledWith(adUnitSetupChecks.validateBannerMediaType, adUnits[1]);
+ });
+
+ it('should delete mediaTypes.banner object if it\'s property sizeConfig is not declared as an array', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ const badSizeConfig = {
+ minViewPort: [0, 0], sizes: [300, 400]
+ };
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook(adUnits), mediaTypes.banner should be defined
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook(adUnits), mediaTypes.banner property should be deleted
+ expect(validatedAdUnits.length).to.not.have.property('banner');
+ });
+
+ it('should log an error message if sizeConfig property in mediaTypes.banner object is not declared as an array', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ // badSizeConfig is NOT defined as an Array
+ const badSizeConfig = {
+ minViewPort: [0, 0], sizes: [300, 400]
+ };
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`);
+ });
+
+ it('should delete mediaTypes.banner object if it\'s property sizeConfig does not contain the required properties "minViewPort" and "sizes"', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[2] does not contain the required "sizes" property
+ const badSizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 250], [300, 600]] },
+ { minViewPort: [1200, 0] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook(adUnits), mediaTypes.banner should be defined
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook(adUnits), mediaTypes.banner property should be deleted
+ expect(validatedAdUnits.length).to.not.have.property('banner');
+ });
+
+ it('should log an error message if sizeConfig property in mediaTypes.banner object does not contain the required properties "minViewPort" and "sizes"', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[2] does not contain the required "sizes" property
+ const badSizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 250], [300, 600]] },
+ { minViewPort: [1200, 0] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[2] is missing required property minViewPort or sizes or both.`);
+ });
+
+ it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[0].minViewPort is an Array of ONE inteter. It should be an array of two integers to be valid, like [0, 0]
+ const badSizeConfig = [
+ { minViewPort: [0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 250], [300, 600]] },
+ { minViewPort: [1200, 0], sizes: [[900, 700], [1000, 1200]] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook(adUnits), mediaTypes.banner should be defined
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook(adUnits), mediaTypes.banner property should be deleted
+ expect(validatedAdUnits.length).to.not.have.property('banner');
+ });
+
+ it('should log an error message if sizeConfig has declared property minViewPort which is not an array of two integers', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[0].minViewPort is an Array of ONE inteter. It should be an array of two integers to be valid, like [0, 0]
+ const badSizeConfig = [
+ { minViewPort: [0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 250], [300, 600]] },
+ { minViewPort: [1200, 0], sizes: [[900, 700], [1000, 1200]] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[0] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`);
+ });
+
+ it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared sizes property which is not in the format, [[vw1, vh1], [vw2, vh2]], where vw is viewport width and vh is viewport height', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[1].sizes is not declared in the correct format. It should be an Array of TWO integers.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [300] },
+ { minViewPort: [1200, 0], sizes: [[900, 700], [1000, 1200]] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook(adUnits), mediaTypes.banner should be defined
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook(adUnits), mediaTypes.banner property should be deleted
+ expect(validatedAdUnits.length).to.not.have.property('banner');
+ });
+
+ it('should log an error message if sizeConfig has declared property sizes which is not in the format, [[vw1, vh1], [vw2, vh2]], where vw is viewport width and vh is viewport height', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[1].sizes is not declared in the correct format. It should be an Array of TWO integers.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [300] },
+ { minViewPort: [1200, 0], sizes: [[900, 700], [1000, 1200]] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[1] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`);
+ });
+
+ it('should convert sizeConfig.sizes to an array of array, i.e., [360, 600] to [[360, 600]]', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[1].sizes is declared as a Array of integers. Correct way to declare is to have it as an Array of Array of Integers, like, [[300, 250]]
+ // Although, the system won't throw error, it'll internall convert it to the format, [[300, 250]]
+ const badSizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [300, 600] },
+ { minViewPort: [1200, 0], sizes: [[900, 700], [1000, 1200]] }
+ ];
+ adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig;
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.banner.sizeConfig[1].sizes).to.deep.equal([[300, 600]]);
+ });
+
+ it('should allow empty array declaration in sizeConfig.sizes to indicate "No valid sizes for this size bucket", and convert it to an array of array, i.e, [] to [[]]', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.banner.sizeConfig[0].sizes).to.deep.equal([[]]);
+ });
+
+ it('should NOT delete mediaTypes.banner object if sizeConfig object is declared correctly', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // before calling checkAdUnitSetupHook, the mediaTypes.banner object should be present on both the Ad Units.
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+ expect(adUnits[1].mediaTypes).to.have.property('banner');
+
+ checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook, the mediaTypes.banner object should still be present for both the Ad Units.
+ expect(adUnits[0].mediaTypes).to.have.property('banner');
+ expect(adUnits[1].mediaTypes).to.have.property('banner');
+ });
+ });
+
+ describe('video mediaTypes checks', function () {
+ beforeEach(function () {
+ sinon.spy(adUnitSetupChecks, 'validateVideoMediaType');
+ });
+
+ afterEach(function () {
+ adUnitSetupChecks.validateVideoMediaType.restore();
+ });
+
+ it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize is present in the Ad Unit', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ checkAdUnitSetupHook(adUnits);
+
+ // since adUntis[1].mediaTypes.video has defined property "playserSize", it should call function "validateVideoMediaType" only once
+ sinon.assert.callCount(adUnitSetupChecks.validateVideoMediaType, 1);
+ sinon.assert.calledWith(adUnitSetupChecks.validateVideoMediaType, adUnits[1]);
+ });
+
+ it('should delete mediaTypes.video.sizeConfig property if sizeConfig is not declared as an array', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig is declared as an object, it should have been an Array.
+ const badSizeConfig = {
+ minViewPort: [0, 0], playerSize: [640, 400]
+ };
+ adUnits[0].mediaTypes.video.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be defined.
+ expect(adUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be deleted.
+ expect(validatedAdUnits[0].mediaTypes.video).to.not.have.property('sizeConfig');
+
+ // check if correct logError is written to the console.
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`);
+ });
+
+ it('should delete mediaTypes.video.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "playerSize"', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[0] doest not contain the required property "playerSize".
+ const badSizeConfig = [
+ { minViewPort: [0, 0] },
+ { minViewPort: [1200, 0], playerSize: [640, 400] }
+ ];
+ adUnits[0].mediaTypes.video.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be defined.
+ expect(adUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be deleted.
+ expect(validatedAdUnits[0].mediaTypes.video).to.not.have.property('sizeConfig');
+
+ // check if correct logError is written to the console.
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`);
+ });
+
+ it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[1].minViewPort is an Array of Integers. It should have been an Array of Array of Integers.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], playerSize: [] },
+ { minViewPort: [1200], playerSize: [640, 400] }
+ ];
+ adUnits[0].mediaTypes.video.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be defined.
+ expect(adUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be deleted.
+ expect(validatedAdUnits[0].mediaTypes.video).to.not.have.property('sizeConfig');
+
+ // check if correct logError is written to the console.
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[1] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`);
+ });
+
+ it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared "playerSize" property which is not in the format, [[vw1, vh1]], where vw is viewport width and vh is viewport height', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[0].playerSize property is declared incorrectly.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], playerSize: [600, 400, 500] },
+ { minViewPort: [1200, 0], playerSize: [640, 400] }
+ ];
+ adUnits[0].mediaTypes.video.sizeConfig = badSizeConfig;
+
+ // before calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be defined.
+ expect(adUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after calling checkAdUnitSetupHook, mediaTypes.video.sizeConfig property should be deleted.
+ expect(validatedAdUnits[0].mediaTypes.video).to.not.have.property('sizeConfig');
+
+ // check if correct logError is written to the console.
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`);
+ });
+
+ it('should convert sizeConfig.playerSize to an array of array, i.e., [360, 600] to [[360, 600]]', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeConfig[] has declared "playerSize" as an Array of Intergers. It should be an Array of Array of Integers, like [[640, 400]].
+ // Although, it won't throw an error if you declare it like this, but internally, the system will convert it to the format listed above.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], playerSize: [] },
+ { minViewPort: [1200, 0], playerSize: [360, 600] }
+ ];
+ adUnits[0].mediaTypes.video.sizeConfig = badSizeConfig;
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ expect(validatedAdUnits[0].mediaTypes.video.sizeConfig[0].playerSize).to.deep.equal([[]]);
+ expect(validatedAdUnits[0].mediaTypes.video.sizeConfig[1].playerSize).to.deep.equal([[360, 600]]);
+ });
+
+ it('should convert mediaTypes.video.playerSize to an array of array, i.e., [360, 600] to [[360, 600]]', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ expect(validatedAdUnits[1].mediaTypes.video.playerSize).to.deep.equal([[300, 460]]);
+ });
+
+ it('should NOT delete mediaTypes.video.sizeConfig property if sizeConfig property is declared correctly', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // before checkAdUnitSetupHook is called
+ expect(adUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+
+ // after checkAdUnitSetupHook is called
+ expect(validatedAdUnits[0].mediaTypes.video).to.have.property('sizeConfig');
+ });
+ });
+
+ describe('native mediaTypes checks', function () {
+ beforeEach(function () {
+ sinon.spy(adUnitSetupChecks, 'validateNativeMediaType');
+ });
+
+ afterEach(function () {
+ adUnitSetupChecks.validateNativeMediaType.restore();
+ });
+
+ it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ checkAdUnitSetupHook(adUnits);
+ sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1);
+ });
+
+ it('should delete mediaTypes.native.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "active"', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ // badSizeConfig[1] doesn't include required property "active"
+ const badSizeConfig = [
+ { minViewPort: [0, 0], active: false },
+ { minViewPort: [1200, 0] }
+ ];
+ adUnits[0].mediaTypes.native.sizeConfig = badSizeConfig;
+ expect(adUnits[0].mediaTypes.native).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig');
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`);
+ });
+
+ it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].minViewPort is NOT an array of TWO integers', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ // badSizeConfig[0].minViewPort is an array of three integers. It should ideally be two integers.
+ const badSizeConfig = [
+ { minViewPort: [0, 0, 0], active: false },
+ { minViewPort: [1200, 0], active: true }
+ ];
+ adUnits[0].mediaTypes.native.sizeConfig = badSizeConfig;
+ expect(adUnits[0].mediaTypes.native).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig');
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`);
+ });
+
+ it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].active is NOT a Boolean', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+
+ // badSizeCofnig[0].active is a String value, it should have been a boolean to be valid.
+ const badSizeConfig = [
+ { minViewPort: [0, 0], active: 'false' },
+ { minViewPort: [1200, 0], active: true }
+ ];
+ adUnits[0].mediaTypes.native.sizeConfig = badSizeConfig;
+ expect(adUnits[0].mediaTypes.native).to.have.property('sizeConfig');
+
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig');
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`);
+ });
+
+ it('should NOT delete mediaTypes.native.sizeConfig property if sizeConfig property is declared correctly', function () {
+ const adUnits = utils.deepClone(AD_UNITS);
+ expect(adUnits[0].mediaTypes.native).to.have.property('sizeConfig');
+ const validatedAdUnits = checkAdUnitSetupHook(adUnits);
+ expect(validatedAdUnits[0].mediaTypes.native).to.have.property('sizeConfig');
+ });
+ });
+ });
+
+ describe('checkBidderSizeConfig(sizeConfig)', function () {
+ it('should return "false" if sizeConfig is NOT declared as an Array at the Bidder level', function () {
+ const sizeConfig = {
+ minViewPort: [600, 0], relevantMediaTypes: ['banner']
+ };
+ expect(checkBidderSizeConfigFormat(sizeConfig)).to.equal(false);
+ });
+
+ it('should return "false" if sizeConfig is declared as an empty Array at the Bidder level', function () {
+ const sizeConfig = [];
+ expect(checkBidderSizeConfigFormat(sizeConfig)).to.equal(false);
+ });
+
+ it('should return "false" if any of the objects in sizeConfig array has not declared the required properties "minViewPort" and/or "relevantMediaTypes"', function () {
+ const sizeConfig_1 = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [800, 0] }
+ ];
+ const sizeConfig_2 = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { relevantMediaTypes: ['banner', 'native'] }
+ ];
+ expect(checkBidderSizeConfigFormat(sizeConfig_1)).to.equal(false);
+ expect(checkBidderSizeConfigFormat(sizeConfig_2)).to.equal(false);
+ });
+
+ it('should return "false" if minViewPort is not declared as an array of two integers', function () {
+ const sizeConfig_1 = [
+ { minViewPort: [], relevantMediaTypes: ['none'] }
+ ];
+ const sizeConfig_2 = [
+ { minViewPort: [300, 0, 0], relevantMediaTypes: ['banner'] }
+ ];
+ expect(checkBidderSizeConfigFormat(sizeConfig_1)).to.equal(false);
+ expect(checkBidderSizeConfigFormat(sizeConfig_2)).to.equal(false);
+ });
+
+ it('should return "false" if relevantMediaTypes is NOT an Array of one or more of these values, "none", "banner", "video", "native"', function () {
+ // declaration of relevantMediaTypes as an empty array is not allowed
+ const sizeConfig_1 = [
+ { minViewPort: [0, 0], relevantMediaTypes: [] }
+ ];
+ // relevantMediaTypes can't be an object. It MUST be declared as an array.
+ const sizeConfig_2 = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [1200, 0], relevantMediaTypes: {} }
+ ];
+ // 'none' and 'banner' can't be together. It should either be only 'none' or 'banner'
+ const sizeConfig_3 = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none', 'banner'] },
+ { minViewPort: [1200, 0], relevantMediaTypes: ['banner'] }
+ ];
+ // relevantMediaTypes can only be an array of ['banner', 'video', 'native'] or any one of them
+ const sizeConfig_4 = [
+ { minViewPort: [1200, 0], relevantMediaTypes: ['banner'] },
+ { minViewPort: [0, 0], relevantMediaTypes: ['video', 'somethingRandom'] },
+ { minViewPort: [1600, 0], relevantMediaTypes: ['native', 'video'] }
+
+ ];
+ expect(checkBidderSizeConfigFormat(sizeConfig_1)).to.equal(false);
+ expect(checkBidderSizeConfigFormat(sizeConfig_2)).to.equal(false);
+ expect(checkBidderSizeConfigFormat(sizeConfig_3)).to.equal(false);
+ expect(checkBidderSizeConfigFormat(sizeConfig_4)).to.equal(false);
+ });
+
+ it('should return "true" if the sizeConfig object is being configured properly at the Bidder level', function () {
+ const sizeConfig = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [600, 0], relevantMediaTypes: ['banner'] },
+ { minViewPort: [1200, 0], relevantMediaTypes: ['banner', 'video'] }
+ ];
+ expect(checkBidderSizeConfigFormat(sizeConfig)).to.equal(true);
+ });
+ });
+
+ describe('isLabelActivated(bidOrAdUnit, activeLabels, adUnitCode)', function () {
+ const labelAny = ['mobile', 'tablet'];
+ const labelAll = ['mobile', 'tablet', 'desktop', 'HD-Tv'];
+ const activeLabels = ['mobile', 'tablet', 'desktop'];
+ const adUnitCode = 'div-gpt-ad-1460505748561-0';
+
+ beforeEach(function () {
+ sinon.spy(utils, 'logWarn');
+ });
+
+ afterEach(function () {
+ utils.logWarn.restore();
+ });
+
+ it('should throw a warning message if both the label operator, "labelAny"/"labelAll" are configured for an Ad Unit', function () {
+ const [adUnits] = utils.deepClone(AD_UNITS);
+ adUnits.labelAny = labelAny;
+ adUnits.labelAll = labelAll;
+
+ isLabelActivated(adUnits, activeLabels, adUnitCode);
+
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: div-gpt-ad-1460505748561-0 has multiple label operators. Using the first declared operator: labelAny`);
+ });
+
+ it('should throw a warning message if both the label operator, "labelAny"/"labelAll" are configured for an Bidder', function () {
+ const [adUnits] = utils.deepClone(AD_UNITS);
+
+ adUnits.bids[0].labelAny = labelAny;
+ adUnits.bids[0].labelAll = labelAll;
+
+ isLabelActivated(adUnits.bids[0], activeLabels, adUnitCode);
+
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Bidder: appnexus in Ad Unit: div-gpt-ad-1460505748561-0 has multiple label operators. Using the first declared operator: labelAny`);
+ });
+
+ it('should give priority to the label operator declared first incase two label operators are found on the same Ad Unit or Bidder', function () {
+ const [adUnits] = utils.deepClone(AD_UNITS);
+ adUnits.labelAll = labelAll;
+ adUnits.labelAny = labelAny;
+
+ // activeAdUnit should be "false"
+ // 'labelAll' -> ['mobile', 'tablet', 'desktop', 'HD-Tv'] will be given priority since it's declared before 'labelAny'
+ // since, activeLabels -> ['mobile', 'tablet', 'desktop'], doesn't include 'HD-Tv', 'isLabelActivated' function should return "false"
+ const activeAdUnit = isLabelActivated(adUnits, activeLabels, adUnitCode);
+ expect(activeAdUnit).to.equal(false);
+
+ // bidder level check
+ adUnits.bids[0].labelAny = labelAny;
+ adUnits.bids[0].labelAll = labelAll;
+
+ // activeBidder should be "true"
+ // 'labelAny' -> ['mobile', 'tablet'] will be given priority since it's declared before 'labelAll'
+ // since, activeLabels -> ['mobile', 'tablet', 'desktop'] and matches atleast one element in labelAny array, so, it'll return true
+ const activeBidder = isLabelActivated(adUnits.bids[0], activeLabels, adUnitCode);
+ expect(activeBidder).to.equal(true);
+ });
+
+ it('should throw a warning log message if "labelAll" operator is declared as an empty array', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+ adUnit.labelAll = [];
+
+ // adUnit level check
+ isLabelActivated(adUnit, activeLabels, adUnitCode);
+
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: div-gpt-ad-1460505748561-0 has declared property labelAll with an empty array. Ad Unit is still enabled!`);
+
+ // bidder level check
+ isLabelActivated(adUnit.bids[0], activeLabels, adUnitCode);
+
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: div-gpt-ad-1460505748561-0 has declared property labelAll with an empty array. Ad Unit is still enabled!`);
+ });
+
+ it('should throw a warning log message if "labelAny" operator is declared as an empty array', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+ adUnit.labelAny = [];
+
+ // adUnit level check
+ isLabelActivated(adUnit, activeLabels, adUnitCode);
+
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: div-gpt-ad-1460505748561-0 has declared property labelAny with an empty array. Ad Unit is still enabled!`);
+
+ // bidder level check
+ isLabelActivated(adUnit.bids[0], activeLabels, adUnitCode);
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: div-gpt-ad-1460505748561-0 has declared property labelAny with an empty array. Ad Unit is still enabled!`);
+ });
+
+ it('should return "true" if label operators are not present on the Ad Unit or Bidder', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+
+ // adUnit level check
+ const activeAdUnit = isLabelActivated(adUnit, activeLabels, adUnitCode);
+ expect(activeAdUnit).to.equal(true);
+
+ // bidder level check
+ const activeBidder = isLabelActivated(adUnit.bids[0], activeLabels, adUnitCode);
+ expect(activeBidder).to.equal(true);
+ });
+
+ it('should filter out the values correctly for the label operators "labelAll"', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+
+ // adUnit level checks
+ adUnit.labelAll = labelAll;
+
+ // const labelAll = ['mobile', 'tablet', 'desktop', 'HD-Tv'];
+ // const activeLabels = ['mobile', 'tablet', 'desktop'];
+
+ const activeAdUnit = isLabelActivated(adUnit, activeLabels, adUnitCode);
+ expect(activeAdUnit).to.equal(false)
+
+ // bidder level checks
+ adUnit.bids[0].labelAll = labelAll;
+
+ const activeBidder = isLabelActivated(adUnit.bids[0], activeLabels, adUnitCode);
+ expect(activeBidder).to.equal(false);
+ });
+
+ it('should filter out the values correctly for the label operators "labelAny"', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+
+ // adUnit level checks
+ adUnit.labelAny = labelAny;
+
+ // const labelAny = ['mobile', 'tablet'];
+ // const activeLabels = ['mobile', 'tablet', 'desktop'];
+
+ const activeAdUnit = isLabelActivated(adUnit, activeLabels, adUnitCode);
+ expect(activeAdUnit).to.equal(true)
+
+ // bidder level checks
+ adUnit.bids[0].labelAny = labelAny;
+
+ const activeBidder = isLabelActivated(adUnit.bids[0], activeLabels, adUnitCode);
+ expect(activeBidder).to.equal(true);
+ });
+ });
+
+ describe('isSizeConfigActivated(mediaType, sizeConfig)', function () {
+ it('should return "false" for the scenarios where sizeConfig should should not get activated', function () {
+ // banner test
+ const sizeConfigBanner = { minViewPort: [0, 0], sizes: [] };
+ const bannerActive = isSizeConfigActivated('banner', sizeConfigBanner);
+ expect(bannerActive).to.equal(false);
+
+ // video test
+ const sizeConfigVideo = { minViewPort: [0, 0], playerSize: [] };
+ const videoActive = isSizeConfigActivated('video', sizeConfigVideo);
+ expect(videoActive).to.equal(false);
+
+ // native test
+ const sizeConfigNative = { minViewPort: [0, 0], active: false };
+ const nativeActive = isSizeConfigActivated('native', sizeConfigNative);
+ expect(nativeActive).to.equal(false);
+ });
+
+ it('should return "true" for the scenarios where sizeConfig should get activated', function () {
+ // banner test
+ const sizeConfigBanner = { minViewPort: [0, 0], sizes: [[300, 600], [970, 1200]] };
+ const bannerActive = isSizeConfigActivated('banner', sizeConfigBanner);
+ expect(bannerActive).to.equal(true);
+
+ // video test
+ const sizeConfigVideo = { minViewPort: [0, 0], playerSize: [[640, 400]] };
+ const videoActive = isSizeConfigActivated('video', sizeConfigVideo);
+ expect(videoActive).to.equal(true);
+
+ // native test
+ const sizeConfigNative = { minViewPort: [0, 0], active: true };
+ const nativeActive = isSizeConfigActivated('native', sizeConfigNative);
+ expect(nativeActive).to.equal(true);
+ });
+
+ it('should return "false" if mediaType does not match "banner", "video" or "native"', function () {
+ const sizeConfig = { minViewPort: [0, 0], sizes: [[300, 600], [970, 1200]] };
+ const active = isSizeConfigActivated('unknownMediaType', sizeConfig);
+ expect(active).to.equal(false);
+ });
+ });
+
+ describe('getActiveSizeBucket(sizeConfig, activeViewport)', function () {
+ it('should return the correct value of size bucket that is active (based on current viewport size) from a given set of size buckets defined in sizeConfig', function () {
+ const sizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [720, 500], sizes: [[300, 200], [300, 400]] },
+ { minViewPort: [1200, 900], sizes: [[720, 400], [1000, 700]] }
+ ];
+
+ // test scenario 1
+ const activeViewportA = [1000, 800];
+ const activeSizeBucketA = getActiveSizeBucket(sizeConfig, activeViewportA);
+ expect(activeSizeBucketA).to.deep.equal([720, 500]);
+
+ // test scenario 2
+ const activeViewportB = [1300, 600];
+ const activeSizeBucketB = getActiveSizeBucket(sizeConfig, activeViewportB);
+ expect(activeSizeBucketB).to.deep.equal([]);
+ });
+ });
+
+ describe('getRelevantMediaTypesForBidder(sizeConfig, activeViewport)', function () {
+ beforeEach(function () {
+ sinon.spy(internal, 'checkBidderSizeConfigFormat');
+ sinon.spy(internal, 'getActiveSizeBucket');
+ });
+
+ afterEach(function () {
+ internal.checkBidderSizeConfigFormat.restore();
+ internal.getActiveSizeBucket.restore();
+ });
+ it('should return an empty array if the bidder sizeConfig object is not formatted correctly', function () {
+ const sizeConfig = [
+ { minViewPort: [], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] }
+ ];
+ const activeViewport = [720, 600];
+ const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeViewport);
+ expect(relevantMediaTypes).to.deep.equal([]);
+ });
+
+ it('should call function checkBidderSizeConfigFormat() once', function () {
+ const sizeConfig = [
+ { minViewPort: [], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] }
+ ];
+ const activeViewport = [720, 600];
+ getRelevantMediaTypesForBidder(sizeConfig, activeViewport);
+
+ sinon.assert.callCount(internal.checkBidderSizeConfigFormat, 1);
+ sinon.assert.calledWith(internal.checkBidderSizeConfigFormat, sizeConfig);
+ });
+
+ it('should call function getActiveSizeBucket() once', function () {
+ const sizeConfig = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] }
+ ];
+ const activeViewport = [720, 600];
+ getRelevantMediaTypesForBidder(sizeConfig, activeViewport);
+
+ sinon.assert.callCount(internal.getActiveSizeBucket, 1);
+ sinon.assert.calledWith(internal.getActiveSizeBucket, sizeConfig, activeViewport);
+ });
+
+ it('should return the array contained in "relevantMediaTypes" property whose sizeBucket matches with the current viewport', function () {
+ const sizeConfig = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] }
+ ];
+ const activeVewport = [720, 600];
+ const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeVewport);
+ expect(relevantMediaTypes).to.deep.equal(['banner', 'video']);
+ });
+ });
+
+ describe('getAdUnitDetail(auctionId, adUnit)', function () {
+ const adUnitDetailFixture_1 = {
+ adUnitCode: 'div-gpt-ad-1460505748561-0',
+ mediaTypes: {},
+ sizeBucketToSizeMap: {},
+ activeViewport: {},
+ transformedMediaTypes: {}
+ };
+ const adUnitDetailFixture_2 = {
+ adUnitCode: 'div-gpt-ad-1460505748561-1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 600]]
+ },
+ video: {
+ context: 'instream',
+ playerSize: [300, 460]
+ }
+ },
+ sizeBucketToSizeMap: {},
+ activeViewport: {},
+ transformedMediaTypes: { banner: {}, video: {} }
+ }
+ beforeEach(function () {
+ sinon
+ .stub(sizeMappingInternalStore, 'getAuctionDetail')
+ .withArgs('a1b2c3')
+ .returns({
+ usingSizeMappingV2: true,
+ adUnits: [adUnitDetailFixture_1]
+ });
+
+ sinon
+ .stub(sizeMappingInternalStore, 'setAuctionDetail')
+ .withArgs('a1b2c3', adUnitDetailFixture_2);
+
+ sinon
+ .stub(internal, 'getFilteredMediaTypes')
+ .withArgs(adUnitDetailFixture_2.mediaTypes)
+ .returns(adUnitDetailFixture_2);
+
+ sinon.spy(utils, 'logInfo');
+ });
+
+ afterEach(function () {
+ sizeMappingInternalStore.getAuctionDetail.restore();
+ sizeMappingInternalStore.setAuctionDetail.restore();
+ internal.getFilteredMediaTypes.restore();
+ utils.logInfo.restore();
+ });
+
+ it('should return adUnit detail object from "sizeMappingInternalStore" if adUnit is alreay present in the store', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+ const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit);
+ sinon.assert.callCount(sizeMappingInternalStore.getAuctionDetail, 1);
+ expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_1);
+ });
+
+ it('should store value in "sizeMappingInterStore" object if adUnit is NOT preset in this object', function () {
+ const [, adUnit] = utils.deepClone(AD_UNITS);
+ const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit);
+ sinon.assert.callCount(sizeMappingInternalStore.setAuctionDetail, 1);
+ sinon.assert.callCount(internal.getFilteredMediaTypes, 1);
+ expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_2);
+ });
+
+ it('should log info message to show the details for activeSizeBucket', function () {
+ const [, adUnit] = utils.deepClone(AD_UNITS);
+ getAdUnitDetail('a1b2c3', adUnit);
+ sinon.assert.callCount(utils.logInfo, 1);
+ sinon.assert.calledWith(utils.logInfo, `SizeMappingV2:: AdUnit: div-gpt-ad-1460505748561-1 - Active size buckets after filtration: `, adUnitDetailFixture_2.sizeBucketToSizeMap);
+ });
+
+ it('should log info message if any of the mediaTypes defined in adUnit.mediaTypes got filtered out', function () {
+ const [, adUnit] = utils.deepClone(AD_UNITS);
+
+ internal.getFilteredMediaTypes.restore();
+
+ const adUnitDetailFixture = {
+ adUnitCode: 'div-gpt-ad-1460505748561-1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 600]]
+ },
+ video: {
+ context: 'instream',
+ playerSize: [300, 460]
+ }
+ },
+ sizeBucketToSizeMap: {},
+ activeViewport: {},
+ transformedMediaTypes: { banner: {} }
+ }
+
+ sinon
+ .stub(internal, 'getFilteredMediaTypes')
+ .withArgs(adUnitDetailFixture.mediaTypes)
+ .returns(adUnitDetailFixture);
+
+ getAdUnitDetail('a1b2c3', adUnit);
+
+ sinon.assert.callCount(utils.logInfo, 2);
+ sinon.assert.calledWith(utils.logInfo.getCall(1), `SizeMappingV2:: AdUnit: div-gpt-ad-1460505748561-1 - mediaTypes that got filtered out: video`);
+ });
+ });
+
+ describe('getFilteredMediaTypes(mediaTypes)', function () {
+ beforeEach(function () {
+ sinon
+ .stub(utils, 'getWindowTop')
+ .returns({
+ innerWidth: 1680,
+ innerHeight: 269
+ });
+
+ sinon.spy(utils, 'logWarn');
+ });
+ afterEach(function () {
+ utils.getWindowTop.restore();
+ utils.logWarn.restore();
+ });
+ it('should return filteredMediaTypes object with all four properties (mediaTypes, transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+ const expectedMediaTypes = {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] }, // remove if < 750px
+ { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px
+ { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px
+ { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px
+ ]
+ },
+ video: {
+ context: 'instream',
+ sizeConfig: [
+ { minViewPort: [0, 0], playerSize: [] },
+ { minViewPort: [800, 0], playerSize: [[640, 400]] },
+ { minViewPort: [1200, 0], playerSize: [] }
+ ]
+ },
+ native: {
+ image: {
+ required: true,
+ sizes: [150, 50]
+ },
+ title: {
+ required: true,
+ len: 80
+ },
+ sponsoredBy: {
+ required: true
+ },
+ clickUrl: {
+ required: true
+ },
+ privacyLink: {
+ required: false
+ },
+ body: {
+ required: true
+ },
+ icon: {
+ required: true,
+ sizes: [50, 50]
+ },
+ sizeConfig: [
+ { minViewPort: [0, 0], active: false },
+ { minViewPort: [600, 0], active: true },
+ { minViewPort: [1000, 0], active: false }
+ ]
+ }
+ };
+ const expectedSizeBucketToSizeMap = {
+ banner: {
+ activeSizeBucket: [1600, 0],
+ activeSizeDimensions: [[1000, 300], [970, 90], [728, 90], [300, 250]]
+ },
+ video: {
+ activeSizeBucket: [1200, 0],
+ activeSizeDimensions: []
+ },
+ native: {
+ activeSizeBucket: [1000, 0],
+ activeSizeDimensions: 'NA'
+ }
+ };
+ const expectedActiveViewport = [1680, 269];
+ const expectedTransformedMediaTypes = {
+ banner: {
+ filteredSizeConfig: [
+ {
+ minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]]
+ }
+ ],
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] },
+ { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] },
+ { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] }
+ ],
+ sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]
+ ]
+ }
+ };
+ const { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = getFilteredMediaTypes(adUnit.mediaTypes);
+ expect(mediaTypes).to.deep.equal(expectedMediaTypes);
+ expect(activeViewport).to.deep.equal(expectedActiveViewport);
+ expect(sizeBucketToSizeMap).to.deep.equal(expectedSizeBucketToSizeMap);
+ expect(transformedMediaTypes).to.deep.equal(expectedTransformedMediaTypes);
+ });
+
+ it('should throw a warning message if Iframe blocks viewport size to be evaluated correctly', function () {
+ const [adUnit] = utils.deepClone(AD_UNITS);
+ utils.getWindowTop.restore();
+ sinon
+ .stub(utils, 'getWindowTop')
+ .throws();
+ getFilteredMediaTypes(adUnit.mediaTypes);
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`);
+ });
+ });
+
+ describe('getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src })', function () {
+ const basic_AdUnit = [{
+ code: 'adUnit1',
+ mediaTypes: {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] }
+ ]
+ }
+ },
+ bids: [{
+ bidder: 'appnexus',
+ params: {
+ placementId: 13144370
+ }
+ }, {
+ bidder: 'rubicon',
+ params: {
+ accountId: 14062,
+ siteId: 70608,
+ zoneId: 498816
+ },
+ sizeConfig: [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner'] }
+ ]
+ }],
+ transactionId: '123456'
+ }];
+ const adUnitDetailFixture = {
+ adUnitCode: 'adUnit1',
+ transactionId: '123456',
+ sizes: [[300, 200], [400, 600]],
+ mediaTypes: {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] }
+ ]
+ }
+ },
+ sizeBucketToSizeMap: {
+ banner: {
+ activeSizeBucket: [[500, 0]],
+ activeSizeDimensions: [[300, 200], [400, 600]]
+ }
+ },
+ activeViewport: [560, 260],
+ transformedMediaTypes: {
+ banner: {
+ filteredSizeConfig: [
+ { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] }
+ ],
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [[]] },
+ { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] }
+ ],
+ sizes: [[300, 200], [400, 600]]
+ }
+ }
+ };
+ beforeEach(function () {
+ sinon
+ .stub(internal, 'getAdUnitDetail')
+ .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', basic_AdUnit[0])
+ .returns(adUnitDetailFixture);
+
+ sinon.spy(internal, 'getRelevantMediaTypesForBidder');
+
+ sinon.spy(utils, 'logInfo');
+ sinon.spy(utils, 'logError');
+ sinon.spy(utils, 'logWarn');
+ });
+
+ afterEach(function () {
+ internal.getAdUnitDetail.restore();
+ internal.getRelevantMediaTypesForBidder.restore();
+ utils.logInfo.restore();
+ utils.logError.restore();
+ utils.logWarn.restore();
+ });
+
+ it('should return an array of bids specific to the bidder', function () {
+ const expectedMediaTypes = {
+ banner: {
+ filteredSizeConfig: [
+ { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] }
+ ],
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [[]] },
+ { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] }
+ ],
+ sizes: [[300, 200], [400, 600]]
+ }
+ };
+
+ const bidRequests_1 = getBids({
+ bidderCode: 'appnexus',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: basic_AdUnit,
+ labels: [],
+ src: 'client'
+ });
+ expect(bidRequests_1[0].mediaTypes).to.deep.equal(expectedMediaTypes);
+ expect(bidRequests_1[0].bidder).to.equal('appnexus');
+
+ const bidRequests_2 = getBids({
+ bidderCode: 'rubicon',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0aa',
+ adUnits: basic_AdUnit,
+ labels: [],
+ src: 'client'
+ });
+ expect(bidRequests_2[0]).to.be.undefined;
+ sinon.assert.callCount(internal.getRelevantMediaTypesForBidder, 1);
+ });
+
+ it('should log an error message if ad unit is disabled because there are no active media types left after size config filtration', function () {
+ internal.getAdUnitDetail.restore();
+
+ const adUnit = utils.deepClone(basic_AdUnit);
+ adUnit[0].mediaTypes.banner.sizeConfig = [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] }
+ ];
+
+ const adUnitDetailFixture = {
+ adUnitCode: 'adUnit1',
+ mediaTypes: {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] }
+ ]
+ }
+ },
+ sizeBucketToSizeMap: {
+ banner: {
+ activeSizeBucket: [0, 0],
+ activeSizeDimensions: [[]]
+ }
+ },
+ activeViewport: [560, 260],
+ transformedMediaTypes: {}
+ };
+
+ sinon
+ .stub(internal, 'getAdUnitDetail')
+ .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0])
+ .returns(adUnitDetailFixture);
+
+ const bidRequests = getBids({
+ bidderCode: 'appnexus',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: adUnit,
+ labels: [],
+ src: 'client'
+ });
+ expect(bidRequests[0]).to.be.undefined;
+ sinon.assert.callCount(utils.logInfo, 1);
+ sinon.assert.calledWith(utils.logInfo, `SizeMappingV2:: Ad Unit: adUnit1 is disabled since there are no active media types after sizeConfig filtration.`);
+ });
+
+ it('should throw an error if bidder level sizeConfig is not configured properly', function () {
+ internal.getAdUnitDetail.restore();
+
+ const adUnit = utils.deepClone(basic_AdUnit);
+ adUnit[0].bids[1].sizeConfig = [
+ { minViewPort: [], relevantMediaTypes: ['none'] },
+ { minViewPort: [700, 0], relevantMediaTypes: ['banner'] }
+ ];
+
+ sinon
+ .stub(internal, 'getAdUnitDetail')
+ .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0])
+ .returns(adUnitDetailFixture);
+
+ const bidRequests = getBids({
+ bidderCode: 'rubicon',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: adUnit,
+ labels: [],
+ src: 'client'
+ });
+
+ expect(bidRequests[0]).to.not.be.undefined;
+ sinon.assert.callCount(utils.logError, 1);
+ sinon.assert.calledWith(utils.logError, `SizeMappingV2:: AdUnit: adUnit1, Bidder: rubicon - sizeConfig is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`);
+ });
+
+ it('should ensure bidder relevantMediaTypes is a subset of active media types at the ad unit level', function () {
+ internal.getAdUnitDetail.restore();
+
+ const adUnit = utils.deepClone(basic_AdUnit);
+ adUnit[0].bids[1].sizeConfig = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [400, 0], relevantMediaTypes: ['banner'] }
+ ];
+
+ sinon
+ .stub(internal, 'getAdUnitDetail')
+ .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0])
+ .returns(adUnitDetailFixture);
+
+ const bidRequests = getBids({
+ bidderCode: 'rubicon',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: adUnit,
+ labels: [],
+ src: 'client'
+ });
+ expect(bidRequests[0]).to.not.be.undefined;
+ expect(bidRequests[0].mediaTypes.banner).to.not.be.undefined;
+ expect(bidRequests[0].mediaTypes.banner.sizes).to.deep.equal([[300, 200], [400, 600]]);
+ });
+
+ it('should logInfo if bidder relevantMediaTypes contains media type that is not active at the ad unit level', function () {
+ internal.getAdUnitDetail.restore();
+
+ const adUnit = utils.deepClone(basic_AdUnit);
+ adUnit[0].mediaTypes = {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 200], [400, 600]] }
+ ]
+ },
+ native: {
+ sizeConfig: [
+ { minViewPort: [0, 0], active: false },
+ { minViewPort: [400, 0], active: true }
+ ]
+ }
+ };
+
+ adUnit[0].bids[1].sizeConfig = [
+ { minViewPort: [0, 0], relevantMediaTypes: ['none'] },
+ { minViewPort: [200, 0], relevantMediaTypes: ['banner'] }
+ ];
+
+ const adUnitDetailFixture = {
+ adUnitCode: 'adUnit1',
+ mediaTypes: {
+ banner: {
+ sizeConfig: [
+ { minViewPort: [0, 0], sizes: [] },
+ { minViewPort: [700, 0], sizes: [[300, 200], [400, 600]] }
+ ]
+ },
+ native: {
+ sizeConfig: [
+ { minViewPort: [0, 0], active: false },
+ { minViewPort: [400, 0], active: true }
+ ]
+ }
+ },
+ sizeBucketToSizeMap: {
+ banner: {
+ activeSizeBucket: [0, 0],
+ activeSizeDimensions: [[]]
+ },
+ native: {
+ activeSizeBucket: [400, 0],
+ activeSizeDimensions: 'NA'
+ }
+ },
+ activeViewport: [560, 260],
+ transformedMediaTypes: {
+ native: {}
+ }
+ };
+
+ sinon
+ .stub(internal, 'getAdUnitDetail')
+ .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0])
+ .returns(adUnitDetailFixture);
+
+ const bidRequests = getBids({
+ bidderCode: 'rubicon',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: adUnit,
+ labels: [],
+ src: 'client'
+ });
+ expect(bidRequests[0]).to.be.undefined;
+ sinon.assert.callCount(utils.logInfo, 1);
+ sinon.assert.calledWith(utils.logInfo, `SizeMappingV2:: AdUnit: adUnit1, Bidder: rubicon - 'relevantMediaTypes' for this bidder does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`);
+ });
+
+ it('should throw a warning if mediaTypes object is not correctly formatted', function () {
+ sinon
+ .stub(utils, 'isValidMediaTypes')
+ .returns(false);
+
+ getBids({
+ bidderCode: 'appnexus',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: basic_AdUnit,
+ labels: [],
+ src: 'client'
+ });
+ sinon.assert.callCount(utils.logWarn, 1);
+ sinon.assert.calledWith(utils.logWarn, `SizeMappingV2:: Ad Unit: adUnit1 has declared invalid mediaTypes or has not declared a mediaTypes property`);
+
+ utils.isValidMediaTypes.restore();
+ });
+
+ it('should log a message if ad unit is disabled due to a failing label check', function () {
+ sinon
+ .stub(internal, 'isLabelActivated')
+ .onFirstCall()
+ .returns(false);
+
+ getBids({
+ bidderCode: 'appnexus',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: basic_AdUnit,
+ labels: [],
+ src: 'client'
+ });
+
+ sinon.assert.callCount(utils.logInfo, 1);
+ sinon.assert.calledWith(utils.logInfo, `SizeMappingV2:: Ad Unit: adUnit1 is disabled due to failing label check.`);
+
+ internal.isLabelActivated.restore();
+ });
+
+ it('should log a message if bidder is disabled due to a failing label check', function () {
+ const stub = sinon.stub(internal, 'isLabelActivated')
+ stub.onFirstCall().returns(true);
+ stub.onSecondCall().returns(false);
+
+ getBids({
+ bidderCode: 'appnexus',
+ auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e',
+ bidderRequestId: '393a43193a0ac',
+ adUnits: basic_AdUnit,
+ labels: [],
+ src: 'client'
+ });
+
+ sinon.assert.callCount(utils.logInfo, 1);
+ sinon.assert.calledWith(utils.logInfo, `SizeMappingV2:: AdUnit: adUnit1, Bidder: appnexus - Label check for this bidder has failed. This bidder is disabled.`);
+
+ internal.isLabelActivated.restore();
+ })
+ });
+});