UNPKG

mk9-prebid

Version:

Header Bidding Management Library

1,307 lines (1,184 loc) 39.2 kB
import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import * as videoCache from 'src/videoCache.js'; import * as auction from 'src/auction.js'; import { ADPOD } from 'src/mediaTypes.js'; import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod.js'; let expect = require('chai').expect; describe('adpod.js', function () { let logErrorStub; let logWarnStub; let logInfoStub; describe('callPrebidCacheHook', function () { let callbackResult; let clock; let addBidToAuctionStub; let doCallbacksIfTimedoutStub; let storeStub; let afterBidAddedSpy; let auctionBids = []; let callbackFn = function() { callbackResult = true; }; let auctionInstance = { getAuctionStatus: function() { return auction.AUCTION_IN_PROGRESS; } } const fakeStoreFn = function(bids, callback) { let payload = []; bids.forEach(bid => payload.push({uuid: bid.customCacheKey})); callback(null, payload); }; beforeEach(function() { callbackResult = null; afterBidAddedSpy = sinon.spy(); storeStub = sinon.stub(videoCache, 'store'); logWarnStub = sinon.stub(utils, 'logWarn'); logInfoStub = sinon.stub(utils, 'logInfo'); addBidToAuctionStub = sinon.stub(auction, 'addBidToAuction').callsFake(function (auctionInstance, bid) { auctionBids.push(bid); }); doCallbacksIfTimedoutStub = sinon.stub(auction, 'doCallbacksIfTimedout'); clock = sinon.useFakeTimers(); config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' } }); }); afterEach(function() { storeStub.restore(); logWarnStub.restore(); logInfoStub.restore(); addBidToAuctionStub.restore(); doCallbacksIfTimedoutStub.restore(); clock.restore(); config.resetConfig(); auctionBids = []; }) it('should redirect back to the original function if bid is not an adpod video', function () { let bid = { adId: 'testAdId_123', mediaType: 'video' }; let bidderRequest = { adUnitCode: 'adUnit_123', mediaTypes: { video: { context: 'outstream' } } } callPrebidCacheHook(callbackFn, auctionInstance, bid, function () {}, bidderRequest); expect(callbackResult).to.equal(true); }); it('should immediately add the adpod bid to auction if adpod.deferCaching in config is true', function() { config.setConfig({ adpod: { deferCaching: true, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'adId01277', auctionId: 'no_defer_123', mediaType: 'video', cpm: 5, pbMg: '5.00', adserverTargeting: { hb_pb: '5.00' }, meta: { adServerCatId: 'test' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidResponse2 = { adId: 'adId46547', auctionId: 'no_defer_123', mediaType: 'video', cpm: 12, pbMg: '12.00', adserverTargeting: { hb_pb: '12.00' }, meta: { adServerCatId: 'value' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_1', auctionId: 'no_defer_123', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 300, durationRangeSec: [15, 30, 45], requireExactDuration: false } }, }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); // check if bid adsereverTargeting is setup expect(callbackResult).to.be.null; expect(storeStub.called).to.equal(false); expect(afterBidAddedSpy.calledTwice).to.equal(true); expect(auctionBids.length).to.equal(2); expect(auctionBids[0].adId).to.equal(bidResponse1.adId); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal(bidResponse2.adId); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); }); it('should send prebid cache call once bid queue is full', function () { storeStub.callsFake(fakeStoreFn); config.setConfig({ adpod: { bidQueueSizeLimit: 2, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'adId123', auctionId: 'full_abc123', mediaType: 'video', cpm: 10, pbMg: '10.00', adserverTargeting: { hb_pb: '10.00' }, meta: { adServerCatId: 'airline' }, video: { context: ADPOD, durationSeconds: 20, durationBucket: 30 } }; let bidResponse2 = { adId: 'adId234', auctionId: 'full_abc123', mediaType: 'video', cpm: 15, pbMg: '15.00', adserverTargeting: { hb_pb: '15.00' }, meta: { adServerCatId: 'airline' }, video: { context: ADPOD, durationSeconds: 25, durationBucket: 30 } }; let bidderRequest = { adUnitCode: 'adpod_1', auctionId: 'full_abc123', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); expect(auctionBids.length).to.equal(2); expect(auctionBids[0].adId).to.equal('adId123'); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('adId234'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should send prebid cache call after set period of time (even if queue is not full)', function () { storeStub.callsFake(fakeStoreFn); config.setConfig({ adpod: { bidQueueSizeLimit: 2, bidQueueTimeDelay: 30, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse = { adId: 'adId234', auctionId: 'timer_abc234', mediaType: 'video', cpm: 15, pbMg: '15.00', adserverTargeting: { hb_pb: '15.00' }, meta: { adServerCatId: 'airline' }, video: { context: ADPOD, durationSeconds: 30, durationBucket: 30 } }; let bidderRequest = { adUnitCode: 'adpod_2', auctionId: 'timer_abc234', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30], requireExactDuration: true } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse, afterBidAddedSpy, bidderRequest); clock.tick(31); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(1); expect(auctionBids[0].adId).to.equal('adId234'); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { storeStub.callsFake(fakeStoreFn); config.setConfig({ adpod: { bidQueueSizeLimit: 2, bidQueueTimeDelay: 30, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'multi_ad1', auctionId: 'multi_call_abc345', mediaType: 'video', cpm: 15, pbMg: '15.00', adserverTargeting: { hb_pb: '15.00' }, meta: { adServerCatId: 'airline' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidResponse2 = { adId: 'multi_ad2', auctionId: 'multi_call_abc345', mediaType: 'video', cpm: 15, pbMg: '15.00', adserverTargeting: { hb_pb: '15.00' }, meta: { adServerCatId: 'news' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidResponse3 = { adId: 'multi_ad3', auctionId: 'multi_call_abc345', mediaType: 'video', cpm: 10, pbMg: '10.00', adserverTargeting: { hb_pb: '10.00' }, meta: { adServerCatId: 'sports' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_3', auctionId: 'multi_call_abc345', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse3, afterBidAddedSpy, bidderRequest); clock.next(); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledThrice).to.equal(true); expect(storeStub.calledTwice).to.equal(true); expect(auctionBids.length).to.equal(3); expect(auctionBids[0].adId).to.equal('multi_ad1'); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('multi_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[2].adId).to.equal('multi_ad3'); expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { storeStub.callsFake(fakeStoreFn); config.setConfig({ adpod: { bidQueueSizeLimit: 2, bidQueueTimeDelay: 30, deferCaching: false, brandCategoryExclusion: false } }); let bidResponse1 = { adId: 'nocat_ad1', auctionId: 'no_category_abc345', mediaType: 'video', cpm: 10, pbMg: '10.00', adserverTargeting: { hb_pb: '10.00' }, meta: { adServerCatId: undefined }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidResponse2 = { adId: 'nocat_ad2', auctionId: 'no_category_abc345', mediaType: 'video', cpm: 15, pbMg: '15.00', adserverTargeting: { hb_pb: '15.00' }, meta: { adServerCatId: undefined }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_4', auctionId: 'no_category_abc345', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); expect(storeStub.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(2); expect(auctionBids[0].adId).to.equal('nocat_ad1'); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('nocat_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { storeStub.callsFake(fakeStoreFn); config.setConfig({ adpod: { bidQueueSizeLimit: 2, bidQueueTimeDelay: 30, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'missingCat_ad1', auctionId: 'missing_category_abc345', mediaType: 'video', cpm: 10, meta: { adServerCatId: undefined }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_5', auctionId: 'missing_category_abc345', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledOnce).to.equal(true); expect(storeStub.called).to.equal(false); expect(logWarnStub.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(0); }); it('should not add bid to auction when Prebid Cache detects an existing key', function () { storeStub.callsFake(function(bids, callback) { let payload = []; bids.forEach(bid => payload.push({uuid: bid.customCacheKey})); // fake a duplicate bid response from PBC (sets an empty string for the uuid) payload[1].uuid = ''; callback(null, payload); }); config.setConfig({ adpod: { bidQueueSizeLimit: 2, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'dup_ad_1', auctionId: 'duplicate_def123', mediaType: 'video', cpm: 5, pbMg: '5.00', adserverTargeting: { hb_pb: '5.00' }, meta: { adServerCatId: 'tech' }, video: { context: ADPOD, durationSeconds: 45, durationBucket: 45 } }; let bidResponse2 = { adId: 'dup_ad_2', auctionId: 'duplicate_def123', mediaType: 'video', cpm: 5, pbMg: '5.00', adserverTargeting: { hb_pb: '5.00' }, meta: { adServerCatId: 'tech' }, video: { context: ADPOD, durationSeconds: 45, durationBucket: 45 } }; let bidderRequest = { adUnitCode: 'adpod_4', auctionId: 'duplicate_def123', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30, 45], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledTwice).to.equal(true); expect(storeStub.calledOnce).to.equal(true); expect(logInfoStub.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(1); expect(auctionBids[0].adId).to.equal('dup_ad_1'); expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bids to auction if PBC returns an error', function() { storeStub.callsFake(function(bids, callback) { let payload = []; let errmsg = 'invalid json'; callback(errmsg, payload); }); config.setConfig({ adpod: { bidQueueSizeLimit: 2, deferCaching: false, brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'err_ad_1', auctionId: 'error_xyz123', mediaType: 'video', cpm: 5, meta: { adServerCatId: 'tech' }, video: { context: ADPOD, durationSeconds: 30, durationBucket: 30 } }; let bidResponse2 = { adId: 'err_ad_2', auctionId: 'error_xyz123', mediaType: 'video', cpm: 5, meta: { adServerCatId: 'tech' }, video: { context: ADPOD, durationSeconds: 30, durationBucket: 30 } }; let bidderRequest = { adUnitCode: 'adpod_5', auctionId: 'error_xyz123', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 120, durationRangeSec: [15, 30, 45], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); expect(doCallbacksIfTimedoutStub.calledTwice).to.equal(true); expect(logWarnStub.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(0); }); it('should use bid.adserverTargeting.hb_pb when custom price granularity is configured', function() { storeStub.callsFake(fakeStoreFn); const customConfigObject = { 'buckets': [{ 'precision': 2, // default is 2 if omitted - means 2.1234 rounded to 2 decimal places = 2.12 'max': 5, 'increment': 0.01 // from $0 to $5, 1-cent increments }, { 'precision': 2, 'max': 8, 'increment': 0.05 // from $5 to $8, round down to the previous 5-cent increment }, { 'precision': 2, 'max': 40, 'increment': 0.5 // from $8 to $40, round down to the previous 50-cent increment }] }; config.setConfig({ priceGranularity: customConfigObject, adpod: { brandCategoryExclusion: true } }); let bidResponse1 = { adId: 'cat_ad1', auctionId: 'test_category_abc345', mediaType: 'video', cpm: 15, pbAg: '15.00', pbCg: '15.00', pbDg: '15.00', pbHg: '15.00', pbLg: '5.00', pbMg: '15.00', adserverTargeting: { hb_pb: '15.00', }, meta: { adServerCatId: 'test' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_5', auctionId: 'test_category_abc345', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 45, durationRangeSec: [15, 30], requireExactDuration: false } } }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); expect(callbackResult).to.be.null; expect(afterBidAddedSpy.calledOnce).to.equal(true); expect(storeStub.called).to.equal(false); expect(auctionBids.length).to.equal(1); }); it('should set deal tier in place of cpm when prioritzeDeals is true', function() { config.setConfig({ adpod: { deferCaching: true, brandCategoryExclusion: true, prioritizeDeals: true, dealTier: { 'appnexus': { 'prefix': 'tier', 'minDealTier': 5 } } } }); let bidResponse1 = { adId: 'adId01277', auctionId: 'no_defer_123', mediaType: 'video', bidderCode: 'appnexus', cpm: 5, pbMg: '5.00', adserverTargeting: { hb_pb: '5.00' }, meta: { adServerCatId: 'test' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15, dealTier: 7 } }; let bidResponse2 = { adId: 'adId46547', auctionId: 'no_defer_123', mediaType: 'video', bidderCode: 'appnexus', cpm: 12, pbMg: '12.00', adserverTargeting: { hb_pb: '12.00' }, meta: { adServerCatId: 'value' }, video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 } }; let bidderRequest = { adUnitCode: 'adpod_1', auctionId: 'no_defer_123', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 300, durationRangeSec: [15, 30, 45], requireExactDuration: false } }, }; callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('tier7_test_15s'); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); }) }); describe('checkAdUnitSetupHook', function () { let results; let callbackFn = function (adUnits) { results = adUnits; }; beforeEach(function () { logWarnStub = sinon.stub(utils, 'logWarn'); results = null; }); afterEach(function() { utils.logWarn.restore(); }); it('removes an incorrectly setup adpod adunit - required fields are missing', function() { let adUnits = [{ code: 'test1', mediaTypes: { video: { context: ADPOD } } }, { code: 'test2', mediaTypes: { video: { context: 'instream' } } }]; checkAdUnitSetupHook(callbackFn, adUnits); expect(results).to.deep.equal([{ code: 'test2', mediaTypes: { video: { context: 'instream' } } }]); expect(logWarnStub.calledOnce).to.equal(true); }); it('removes an incorrectly setup adpod adunit - required fields are using invalid values', function() { let adUnits = [{ code: 'test1', mediaTypes: { video: { context: ADPOD, durationRangeSec: [-5, 15, 30, 45], adPodDurationSec: 300 } } }]; checkAdUnitSetupHook(callbackFn, adUnits); expect(results).to.deep.equal([]); expect(logWarnStub.calledOnce).to.equal(true); adUnits[0].mediaTypes.video.durationRangeSec = [15, 30, 45]; adUnits[0].mediaTypes.video.adPodDurationSec = 0; checkAdUnitSetupHook(callbackFn, adUnits); expect(results).to.deep.equal([]); expect(logWarnStub.calledTwice).to.equal(true); }); it('removes an incorrectly setup adpod adunit - attempting to use multi-format adUnit', function() { let adUnits = [{ code: 'multi_test1', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] }, video: { context: 'adpod', playerSize: [[300, 250]], durationRangeSec: [15, 30, 45], adPodDurationSec: 300 } } }]; checkAdUnitSetupHook(callbackFn, adUnits); expect(results).to.deep.equal([]); expect(logWarnStub.calledOnce).to.equal(true); }); it('accepts mixed set of adunits', function() { let adUnits = [{ code: 'test3', mediaTypes: { video: { context: ADPOD, playerSize: [[300, 300]], adPodDurationSec: 360, durationRangeSec: [15, 30, 45], requireExactDuration: true } } }, { code: 'test4', mediaTypes: { banner: { sizes: [[300, 250]] } } }]; checkAdUnitSetupHook(callbackFn, adUnits); expect(results).to.deep.equal(adUnits); expect(logWarnStub.called).to.equal(false); }); }); describe('checkVideoBidSetupHook', function () { let callbackResult; let bailResult; const callbackFn = { call: function(context, bid) { callbackResult = bid; }, bail: function(result) { bailResult = result; } } const adpodTestBid = { video: { context: ADPOD, durationSeconds: 15, durationBucket: 15 }, meta: { primaryCatId: 'testCategory_123' }, vastXml: '<VAST>test XML here</VAST>' }; const bidderRequestNoExact = { mediaTypes: { video: { context: ADPOD, playerSize: [[300, 400]], durationRangeSec: [15, 45], requireExactDuration: false, adPodDurationSec: 300 } } }; const bidderRequestWithExact = { mediaTypes: { video: { context: ADPOD, playerSize: [[300, 400]], durationRangeSec: [15, 30, 45, 60], requireExactDuration: true, adPodDurationSec: 300 } } }; beforeEach(function() { callbackResult = null; bailResult = null; config.setConfig({ cache: { url: 'http://test.cache.url/endpoint' }, adpod: { brandCategoryExclusion: true } }); logWarnStub = sinon.stub(utils, 'logWarn'); logErrorStub = sinon.stub(utils, 'logError'); }); afterEach(function() { config.resetConfig(); logWarnStub.restore(); logErrorStub.restore(); }) it('redirects to original function for non-adpod type video bids', function() { let bannerTestBid = { mediaType: 'video' }; checkVideoBidSetupHook(callbackFn, bannerTestBid, {}, {}, 'instream'); expect(callbackResult).to.deep.equal(bannerTestBid); expect(bailResult).to.be.null; expect(logErrorStub.called).to.equal(false); }); it('returns true when adpod bid is properly setup', function() { config.setConfig({ cache: { url: 'http://test.cache.url/endpoint' }, adpod: { brandCategoryExclusion: false } }); let goodBid = utils.deepClone(adpodTestBid); goodBid.meta.primaryCatId = undefined; checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(true); expect(logErrorStub.called).to.equal(false); }); it('returns true when adpod bid is missing iab category while brandCategoryExclusion in config is false', function() { let goodBid = utils.deepClone(adpodTestBid); checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(true); expect(logErrorStub.called).to.equal(false); }); it('returns false when a required property from an adpod bid is missing', function() { function testInvalidAdpodBid(badTestBid, shouldErrorBeLogged) { checkVideoBidSetupHook(callbackFn, badTestBid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bailResult).to.equal(false); expect(logErrorStub.called).to.equal(shouldErrorBeLogged); } let noCatBid = utils.deepClone(adpodTestBid); noCatBid.meta.primaryCatId = undefined; testInvalidAdpodBid(noCatBid, false); let noContextBid = utils.deepClone(adpodTestBid); delete noContextBid.video.context; testInvalidAdpodBid(noContextBid, false); let wrongContextBid = utils.deepClone(adpodTestBid); wrongContextBid.video.context = 'instream'; testInvalidAdpodBid(wrongContextBid, false); let noDurationBid = utils.deepClone(adpodTestBid); delete noDurationBid.video.durationSeconds; testInvalidAdpodBid(noDurationBid, false); config.resetConfig(); let noCacheUrlBid = utils.deepClone(adpodTestBid); testInvalidAdpodBid(noCacheUrlBid, true); }); describe('checkBidDuration', function() { const basicBid = { video: { context: ADPOD, durationSeconds: 30 }, meta: { primaryCatId: 'testCategory_123' }, vastXml: '<VAST/>' }; it('when requireExactDuration is true', function() { let goodBid = utils.deepClone(basicBid); checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestWithExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(goodBid.video.durationBucket).to.equal(30); expect(bailResult).to.equal(true); expect(logWarnStub.called).to.equal(false); let badBid = utils.deepClone(basicBid); badBid.video.durationSeconds = 14; checkVideoBidSetupHook(callbackFn, badBid, bidderRequestWithExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(badBid.video.durationBucket).to.be.undefined; expect(bailResult).to.equal(false); expect(logWarnStub.calledOnce).to.equal(true); }); it('when requireExactDuration is false and bids are bucketed properly', function() { function testRoundingForGoodBId(bid, bucketValue) { checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bid.video.durationBucket).to.equal(bucketValue); expect(bailResult).to.equal(true); expect(logWarnStub.called).to.equal(false); } let goodBid45 = utils.deepClone(basicBid); goodBid45.video.durationSeconds = 45; testRoundingForGoodBId(goodBid45, 45); let goodBid30 = utils.deepClone(basicBid); goodBid30.video.durationSeconds = 30; testRoundingForGoodBId(goodBid30, 45); let goodBid10 = utils.deepClone(basicBid); goodBid10.video.durationSeconds = 10; testRoundingForGoodBId(goodBid10, 15); let goodBid16 = utils.deepClone(basicBid); goodBid16.video.durationSeconds = 16; testRoundingForGoodBId(goodBid16, 15); let goodBid47 = utils.deepClone(basicBid); goodBid47.video.durationSeconds = 47; testRoundingForGoodBId(goodBid47, 45); }); it('when requireExactDuration is false and bid duration exceeds listed buckets', function() { function testRoundingForBadBid(bid) { checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); expect(callbackResult).to.be.null; expect(bid.video.durationBucket).to.be.undefined; expect(bailResult).to.equal(false); expect(logWarnStub.called).to.equal(true); } let badBid100 = utils.deepClone(basicBid); badBid100.video.durationSeconds = 100; testRoundingForBadBid(badBid100); let badBid48 = utils.deepClone(basicBid); badBid48.video.durationSeconds = 48; testRoundingForBadBid(badBid48); }); }); }); describe('adpodSetConfig', function () { let logWarnStub; beforeEach(function() { logWarnStub = sinon.stub(utils, 'logWarn'); }); afterEach(function () { logWarnStub.restore(); }); it('should log a warning when values other than numbers are used in setConfig', function() { adpodSetConfig({ bidQueueSizeLimit: '2', bidQueueTimeDelay: '50' }); expect(logWarnStub.calledTwice).to.equal(true); }); it('should log a warning when numbers less than or equal to zero are used in setConfig', function() { adpodSetConfig({ bidQueueSizeLimit: 0, bidQueueTimeDelay: -2 }); expect(logWarnStub.calledTwice).to.equal(true); }); it('should not log any warning when using a valid config', function() { adpodSetConfig({ bidQueueSizeLimit: 10 }); expect(logWarnStub.called).to.equal(false); adpodSetConfig({ bidQueueTimeDelay: 100, bidQueueSizeLimit: 20 }); expect(logWarnStub.called).to.equal(false); }) }); describe('adpod utils', function() { it('should sort bids array', function() { let bids = [{ cpm: 10.12345, adserverTargeting: { hb_pb: '10.00', }, video: { durationBucket: 15 } }, { cpm: 15, adserverTargeting: { hb_pb: '15.00', }, video: { durationBucket: 15 } }, { cpm: 15.00, adserverTargeting: { hb_pb: '15.00', }, video: { durationBucket: 30 } }, { cpm: 5.45, adserverTargeting: { hb_pb: '5.00', }, video: { durationBucket: 5 } }, { cpm: 20.1234567, adserverTargeting: { hb_pb: '20.10', }, video: { durationBucket: 60 } }] bids.sort(sortByPricePerSecond); let sortedBids = [{ cpm: 15, adserverTargeting: { hb_pb: '15.00', }, video: { durationBucket: 15 } }, { cpm: 5.45, adserverTargeting: { hb_pb: '5.00', }, video: { durationBucket: 5 } }, { cpm: 10.12345, adserverTargeting: { hb_pb: '10.00', }, video: { durationBucket: 15 } }, { cpm: 15.00, adserverTargeting: { hb_pb: '15.00', }, video: { durationBucket: 30 } }, { cpm: 20.1234567, adserverTargeting: { hb_pb: '20.10', }, video: { durationBucket: 60 } }] expect(bids).to.include.deep.ordered.members(sortedBids); }); }) });