UNPKG

mk9-prebid

Version:

Header Bidding Management Library

662 lines (583 loc) 21.3 kB
import { expect } from 'chai'; import parse from 'url-parse'; import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo.js'; import adUnit from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { targeting } from 'src/targeting.js'; import { auctionManager } from 'src/auctionManager.js'; import { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import * as adpod from 'modules/adpod.js'; import { server } from 'test/mocks/xhr.js'; const bid = { videoCacheKey: 'abc', adserverTargeting: { hb_uuid: 'abc', hb_cache_id: 'abc', }, }; describe('The DFP video support module', function () { it('should make a legal request URL when given the required params', function () { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bid, params: { 'iu': 'my/adUnit', 'description_url': 'someUrl.com', } })); expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); expect(queryParams).to.have.property('gdfp_req', '1'); expect(queryParams).to.have.property('iu', 'my/adUnit'); expect(queryParams).to.have.property('output', 'vast'); expect(queryParams).to.have.property('sz', '640x480'); expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); }); it('can take an adserver url as a parameter', function () { const bidCopy = utils.deepClone(bid); bidCopy.vastUrl = 'vastUrl.example'; const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, url: 'https://video.adserver.example/', })); expect(url.host).to.equal('video.adserver.example'); const queryObject = utils.parseQS(url.query); expect(queryObject.description_url).to.equal('vastUrl.example'); }); it('requires a params object or url', function () { const url = buildDfpVideoUrl({ adUnit: adUnit, bid: bid, }); expect(url).to.be.undefined; }); it('overwrites url params when both url and params object are given', function () { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bid, url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s', params: { iu: 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.iu).to.equal('my/adUnit'); }); it('should override param defaults with user-provided ones', function () { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bid, params: { 'iu': 'my/adUnit', 'output': 'vast', } })); expect(utils.parseQS(url.query)).to.have.property('output', 'vast'); }); it('should include the cache key and adserver targeting in cust_params', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); it('should include the us_privacy key when USP Consent is available', function () { let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); uspDataHandlerStub.returns('1YYY'); const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.us_privacy).to.equal('1YYY'); uspDataHandlerStub.restore(); }); it('should not include the us_privacy key when USP Consent is not available', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.us_privacy).to.equal(undefined); }); it('should include the GDPR keys when GDPR Consent is available', function () { let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); gdprDataHandlerStub.returns({ gdprApplies: true, consentString: 'consent', addtlConsent: 'moreConsent' }); const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.gdpr).to.equal('1'); expect(queryObject.gdpr_consent).to.equal('consent'); expect(queryObject.addtl_consent).to.equal('moreConsent'); gdprDataHandlerStub.restore(); }); it('should not include the GDPR keys when GDPR Consent is not available', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.gdpr).to.equal(undefined); expect(queryObject.gdpr_consent).to.equal(undefined); expect(queryObject.addtl_consent).to.equal(undefined); }); it('should only include the GDPR keys for GDPR Consent fields with values', function () { let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); gdprDataHandlerStub.returns({ gdprApplies: true, consentString: 'consent', }); const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.gdpr).to.equal('1'); expect(queryObject.gdpr_consent).to.equal('consent'); expect(queryObject.addtl_consent).to.equal(undefined); gdprDataHandlerStub.restore(); }); describe('special targeting unit test', function () { const allTargetingData = { 'hb_format': 'video', 'hb_source': 'client', 'hb_size': '640x480', 'hb_pb': '5.00', 'hb_adid': '2c4f6cc3ba128a', 'hb_bidder': 'testBidder2', 'hb_format_testBidder2': 'video', 'hb_source_testBidder2': 'client', 'hb_size_testBidder2': '640x480', 'hb_pb_testBidder2': '5.00', 'hb_adid_testBidder2': '2c4f6cc3ba128a', 'hb_bidder_testBidder2': 'testBidder2', 'hb_format_appnexus': 'video', 'hb_source_appnexus': 'client', 'hb_size_appnexus': '640x480', 'hb_pb_appnexus': '5.00', 'hb_adid_appnexus': '44e0b5f2e5cace', 'hb_bidder_appnexus': 'appnexus' }; let targetingStub; before(function () { targetingStub = sinon.stub(targeting, 'getAllTargeting'); targetingStub.returns({'video1': allTargetingData}); config.setConfig({ enableSendAllBids: true }); }); after(function () { config.resetConfig(); targetingStub.restore(); }); it('should include all adserver targeting in cust_params if pbjs.enableSendAllBids is true', function () { const adUnitsCopy = utils.deepClone(adUnit); adUnitsCopy.bids.push({ 'bidder': 'testBidder2', 'params': { 'placementId': '9333431', 'video': { 'skipppable': false, 'playback_methods': ['auto_play_sound_off'] } } }); const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnitsCopy, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); expect(customParams).to.have.property('hb_bidder_appnexus', 'appnexus'); expect(customParams).to.have.property('hb_bidder_testBidder2', 'testBidder2'); }); }); it('should merge the user-provided cust_params with the default ones', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit', cust_params: { 'my_targeting': 'foo', }, }, })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('my_targeting', 'foo'); }); it('should merge the user-provided cust-params with the default ones when using url object', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s&cust_params=section%3dblog%26mykey%3dmyvalue' })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('section', 'blog'); expect(customParams).to.have.property('mykey', 'myvalue'); expect(customParams).to.have.property('hb_uuid', 'abc'); expect(customParams).to.have.property('hb_cache_id', 'abc'); }); it('should not overwrite an existing description_url for object input and cache disabled', function () { const bidCopy = utils.deepClone(bid); bidCopy.vastUrl = 'vastUrl.example'; const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { iu: 'my/adUnit', description_url: 'descriptionurl.example' } })); const queryObject = utils.parseQS(url.query); expect(queryObject.description_url).to.equal('descriptionurl.example'); }); it('should work with nobid responses', function () { const url = buildDfpVideoUrl({ adUnit: adUnit, params: { 'iu': 'my/adUnit' } }); expect(url).to.be.a('string'); }); it('should include hb_uuid and hb_cache_id in cust_params when both keys are exluded from overwritten bidderSettings', function () { const bidCopy = utils.deepClone(bid); delete bidCopy.adserverTargeting.hb_uuid; delete bidCopy.adserverTargeting.hb_cache_id; const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); it('should include hb_uuid and hb_cache_id in cust params from overwritten standard bidderSettings', function () { const bidCopy = utils.deepClone(bid); bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_uuid: 'def', hb_cache_id: 'def' }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, bid: bidCopy, params: { 'iu': 'my/adUnit' } })); const queryObject = utils.parseQS(url.query); const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); expect(customParams).to.have.property('hb_uuid', 'def'); expect(customParams).to.have.property('hb_cache_id', 'def'); }); describe('adpod unit tests', function () { let amStub; let amGetAdUnitsStub; before(function () { let adUnits = [{ code: 'adUnitCode-1', mediaTypes: { video: { context: 'adpod', playerSize: [640, 480], adPodDurationSec: 60, durationRangeSec: [15, 30], requireExactDuration: true } }, bids: [ { bidder: 'appnexus', params: { placementId: 14542875, } } ] }]; amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); amGetAdUnitsStub.returns(adUnits); amStub = sinon.stub(auctionManager, 'getBidsReceived'); }); beforeEach(function () { config.setConfig({ adpod: { brandCategoryExclusion: true, deferCaching: false } }); }) afterEach(function() { config.resetConfig(); }); after(function () { amGetAdUnitsStub.restore(); amStub.restore(); }); it('should return masterTag url', function() { amStub.returns(getBidsReceived()); let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); uspDataHandlerStub.returns('1YYY'); let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); gdprDataHandlerStub.returns({ gdprApplies: true, consentString: 'consent', addtlConsent: 'moreConsent' }); let url; parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', callback: handleResponse, params: { 'iu': 'my/adUnit', 'description_url': 'someUrl.com', } })); function handleResponse(err, masterTag) { if (err) { return; } url = parse(masterTag); expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); expect(queryParams).to.have.property('gdfp_req', '1'); expect(queryParams).to.have.property('iu', 'my/adUnit'); expect(queryParams).to.have.property('output', 'vast'); expect(queryParams).to.have.property('sz', '640x480'); expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); expect(queryParams).to.have.property('us_privacy', '1YYY'); expect(queryParams).to.have.property('gdpr', '1'); expect(queryParams).to.have.property('gdpr_consent', 'consent'); expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); uspDataHandlerStub.restore(); gdprDataHandlerStub.restore(); } }); it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { config.setConfig({ adpod: { brandCategoryExclusion: false, } }); function getBids() { let bids = [ createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), ]; bids.forEach((bid) => { delete bid.meta; }); return bids; } amStub.returns(getBids()); let url; parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', callback: handleResponse, params: { 'iu': 'my/adUnit', 'description_url': 'someUrl.com', } })); function handleResponse(err, masterTag) { if (err) { return; } url = parse(masterTag); expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); const queryParams = utils.parseQS(url.query); expect(queryParams).to.have.property('correlator'); expect(queryParams).to.have.property('description_url', 'someUrl.com'); expect(queryParams).to.have.property('env', 'vp'); expect(queryParams).to.have.property('gdfp_req', '1'); expect(queryParams).to.have.property('iu', 'my/adUnit'); expect(queryParams).to.have.property('output', 'xml_vast3'); expect(queryParams).to.have.property('sz', '640x480'); expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); } }); it('should handle error when cache fails', function() { config.setConfig({ adpod: { brandCategoryExclusion: true, deferCaching: true } }); amStub.returns(getBidsReceived()); parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', callback: handleResponse, params: { 'iu': 'my/adUnit', 'description_url': 'someUrl.com', } })); server.requests[0].respond(503, { 'Content-Type': 'plain/text', }, 'The server could not save anything at the moment.'); function handleResponse(err, masterTag) { expect(masterTag).to.be.null; expect(err).to.be.an('error'); } }); }) }); function getBidsReceived() { return [ createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), ] } function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { return { 'bidderCode': 'appnexus', 'width': 640, 'height': 360, 'statusMessage': 'Bid available', 'adId': '28f24ced14586c', 'mediaType': 'video', 'source': 'client', 'requestId': '28f24ced14586c', 'cpm': cpm, 'creativeId': 97517771, 'currency': 'USD', 'netRevenue': true, 'ttl': 3600, 'adUnitCode': adUnitCode, 'video': { 'context': 'adpod', 'durationBucket': durationBucket }, 'appnexus': { 'buyerMemberId': 9325 }, 'vastUrl': 'http://some-vast-url.com', 'vastImpUrl': 'http://some-vast-imp-url.com', 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', 'responseTimestamp': 1548442460888, 'requestTimestamp': 1548442460827, 'bidder': 'appnexus', 'timeToRespond': 61, 'pbLg': '5.00', 'pbMg': '5.00', 'pbHg': '5.00', 'pbAg': '5.00', 'pbDg': '5.00', 'pbCg': '', 'size': '640x360', 'adserverTargeting': { 'hb_bidder': 'appnexus', 'hb_adid': '28f24ced14586c', 'hb_pb': hbpb, 'hb_size': '640x360', 'hb_source': 'client', 'hb_format': 'video', 'hb_pb_cat_dur': priceIndustryDuration, 'hb_cache_id': uuid }, 'customCacheKey': `${priceIndustryDuration}_${uuid}`, 'meta': { 'primaryCatId': 'iab-1', 'adServerCatId': label }, 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' } }