UNPKG

mk9-prebid

Version:

Header Bidding Management Library

1,095 lines (881 loc) 46.1 kB
import { expect } from 'chai'; import { spec } from 'modules/bluebillywigBidAdapter.js'; import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; import { deepClone, deepAccess } from 'src/utils.js'; import { config } from 'src/config.js'; import { VIDEO } from 'src/mediaTypes.js'; const BB_CONSTANTS = { BIDDER_CODE: 'bluebillywig', AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', DEFAULT_TIMEOUT: 5000, DEFAULT_TTL: 300, DEFAULT_WIDTH: 768, DEFAULT_HEIGHT: 432, DEFAULT_NET_REVENUE: true }; describe('BlueBillywigAdapter', () => { describe('isBidRequestValid', () => { const baseValidBid = { bidder: BB_CONSTANTS.BIDDER_CODE, params: { accountId: 123, publicationName: 'bbprebid.dev', rendererCode: 'glorious_renderer', connections: [ BB_CONSTANTS.BIDDER_CODE ], bluebillywig: {} }, mediaTypes: { video: { context: 'outstream' } } }; it('should return true when required params found', () => { expect(spec.isBidRequestValid(baseValidBid)).to.equal(true); }); it('should return false when params missing', () => { const bid = deepClone(baseValidBid); delete bid.params; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when publicationName is missing', () => { const bid = deepClone(baseValidBid); delete bid.params.publicationName; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when publicationName is not a string', () => { const bid = deepClone(baseValidBid); bid.params.publicationName = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.publicationName = false; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.publicationName = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.publicationName = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when publicationName is formatted poorly', () => { const bid = deepClone(baseValidBid); bid.params.publicationName = 'bb.'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.publicationName = 'bb-test'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.publicationName = '?'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when renderer is not specified', () => { const bid = deepClone(baseValidBid); delete bid.params.rendererCode; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when renderer is not a string', () => { const bid = deepClone(baseValidBid); bid.params.rendererCode = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererCode = false; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererCode = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererCode = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when renderer is formatted poorly', () => { const bid = deepClone(baseValidBid); bid.params.rendererCode = 'bb.'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererCode = 'bb-test'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererCode = '?'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when accountId is not specified', () => { const bid = deepClone(baseValidBid); delete bid.params.accountId; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when connections is not specified', () => { const bid = deepClone(baseValidBid); delete bid.params.connections; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when connections is not an array', () => { const bid = deepClone(baseValidBid); bid.params.connections = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.connections = false; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.connections = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.connections = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.connections = 'string'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when a connection is missing', () => { const bid = deepClone(baseValidBid); bid.params.connections.push('potatoes'); expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.connections.pop(); delete bid.params[BB_CONSTANTS.BIDDER_CODE]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if bid has no mediaTypes', () => { const bid = deepClone(baseValidBid); delete bid.mediaTypes; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if bid has no mediaTypes.video', () => { const bid = deepClone(baseValidBid); delete bid.mediaTypes[VIDEO]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if bid has no mediaTypes.video.context', () => { const bid = deepClone(baseValidBid); delete bid.mediaTypes[VIDEO].context; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if mediaTypes.video.context is not "outstream"', () => { const bid = deepClone(baseValidBid); bid.mediaTypes[VIDEO].context = 'instream'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if video is specified but is not an object', () => { const bid = deepClone(baseValidBid); bid.params.video = null; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.video = 'string'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.video = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.video = false; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.video = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should fail if rendererSettings is specified but is not an object', () => { const bid = deepClone(baseValidBid); bid.params.rendererSettings = null; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererSettings = 'string'; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererSettings = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererSettings = false; expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.rendererSettings = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); describe('buildRequests', () => { const publicationName = 'bbprebid.dev'; const rendererCode = 'glorious_renderer'; const baseValidBid = { bidder: BB_CONSTANTS.BIDDER_CODE, params: { accountId: 123, publicationName: publicationName, rendererCode: rendererCode, connections: [ BB_CONSTANTS.BIDDER_CODE ], bluebillywig: {} }, mediaTypes: { video: { context: 'outstream' } } }; const baseValidBidRequests = [baseValidBid]; const validBidderRequest = { auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', auctionStart: 1585918458868, bidderCode: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', bids: [{ adUnitCode: 'ad-unit-test', auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', bidId: '1234ab567c89de0', bidRequestsCount: 1, bidder: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', params: baseValidBid.params, sizes: [[768, 432], [640, 480], [630, 360]], transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' }], start: 11585918458869, timeout: 3000 }; it('sends bid request to AUCTION_URL via POST', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); expect(request.url).to.equal(`https://pbs.bluebillywig.com/openrtb2/auction?pub=${publicationName}`); expect(request.method).to.equal('POST'); }); it('sends data as a string', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); expect(request.data).to.be.a('string'); }); it('sends all bid parameters', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); it('builds the base request properly', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.id).to.equal(validBidderRequest.auctionId); expect(payload.source).to.be.an('object'); expect(payload.source.tid).to.equal(validBidderRequest.auctionId); expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT); expect(payload.imp).to.be.an('array'); expect(payload.test).to.be.a('number'); expect(payload).to.have.nested.property('ext.prebid.targeting'); expect(payload.ext.prebid.targeting).to.be.an('object'); expect(payload.ext.prebid.targeting.includewinners).to.equal(true); expect(payload.ext.prebid.targeting.includebidderkeys).to.equal(false); }); it('adds an impression to the payload', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.imp.length).to.equal(1); }); it('adds connections to ext', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.imp[0].ext).to.have.all.keys(['bluebillywig']); }); it('adds gdpr when present', () => { const newValidBidderRequest = deepClone(validBidderRequest); newValidBidderRequest.gdprConsent = { consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', gdprApplies: true }; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('regs.ext.gdpr'); expect(payload.regs.ext.gdpr).to.be.a('number'); expect(payload.regs.ext.gdpr).to.equal(1); expect(payload).to.have.nested.property('user.ext.consent'); expect(payload.user.ext.consent).to.equal(newValidBidderRequest.gdprConsent.consentString); }); it('sets gdpr to 0 when explicitly gdprApplies: false', () => { const newValidBidderRequest = deepClone(validBidderRequest); newValidBidderRequest.gdprConsent = { gdprApplies: false }; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('regs.ext.gdpr'); expect(payload.regs.ext.gdpr).to.be.a('number'); expect(payload.regs.ext.gdpr).to.equal(0); }); it('adds usp_consent when present', () => { const newValidBidderRequest = deepClone(validBidderRequest); newValidBidderRequest.uspConsent = '1YYY'; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('regs.ext.us_privacy'); expect(payload.regs.ext.us_privacy).to.equal(newValidBidderRequest.uspConsent); }); it('sets coppa to 1 when specified in config', () => { config.setConfig({'coppa': true}); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('regs.coppa'); expect(payload.regs.coppa).to.equal(1); config.resetConfig(); }); it('does not set coppa when disabled in the config', () => { config.setConfig({'coppa': false}); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; config.resetConfig(); }); it('does not set coppa when not specified in config', () => { config.resetConfig(); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; }); it('should add window size to request by default', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('device.w'); expect(payload).to.have.nested.property('device.h'); expect(payload.device.w).to.be.a('number'); expect(payload.device.h).to.be.a('number'); }); it('should add site when specified in config', () => { config.setConfig({ site: { name: 'Blue Billywig', domain: 'bluebillywig.com', page: 'https://bluebillywig.com/', publisher: { id: 'abc', name: 'Blue Billywig', domain: 'bluebillywig.com' } } }); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.property('site'); expect(payload).to.have.nested.property('site.name'); expect(payload).to.have.nested.property('site.domain'); expect(payload).to.have.nested.property('site.page'); expect(payload).to.have.nested.property('site.publisher'); expect(payload).to.have.nested.property('site.publisher.id'); expect(payload).to.have.nested.property('site.publisher.name'); expect(payload).to.have.nested.property('site.publisher.domain'); config.resetConfig(); }); it('should add app when specified in config', () => { config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.property('app'); expect(payload).to.have.nested.property('app.bundle'); expect(payload).to.have.nested.property('app.domain'); expect(payload.app.bundle).to.equal('org.prebid.mobile.demoapp'); expect(payload.app.domain).to.equal('prebid.org'); config.resetConfig(); }); it('should add referrerInfo as site when no app is set', () => { const newValidBidderRequest = deepClone(validBidderRequest); newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('site.page'); expect(payload.site.page).to.equal('https://www.bluebillywig.com'); }); it('should not add referrerInfo as site when app is set', () => { config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); const newValidBidderRequest = deepClone(validBidderRequest); newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); expect(payload.site).to.be.undefined; config.resetConfig(); }); it('should add device size to request when specified in config', () => { config.setConfig({ device: { w: 1, h: 1 } }); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('device.w'); expect(payload).to.have.nested.property('device.h'); expect(payload.device.w).to.be.a('number'); expect(payload.device.h).to.be.a('number'); expect(payload.device.w).to.equal(1); expect(payload.device.h).to.equal(1); config.resetConfig(); }); it('should set schain on the request when set on config', () => { const schain = { validation: 'lax', config: { ver: '1.0', complete: 1, nodes: [ { asi: 'indirectseller.com', sid: '00001', hp: 1 } ] } }; const newBaseValidBidRequests = deepClone(baseValidBidRequests); newBaseValidBidRequests[0].schain = schain; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('source.ext.schain'); expect(payload.source.ext.schain).to.deep.equal(schain); }); it('should add currency when specified on the config', () => { config.setConfig({ currency: { adServerCurrency: 'USD' } }); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.property('cur'); expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally config.resetConfig(); }); it('should also take in array for currency on the config', () => { config.setConfig({ currency: { adServerCurrency: ['USD', 'PHP'] } }); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.property('cur'); expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally config.resetConfig(); }); it('should not set cur when currency is not specified on the config', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.cur).to.be.undefined; }); it('should set user ids when present', () => { const userId = { tdid: 123 }; const newBaseValidBidRequests = deepClone(baseValidBidRequests); newBaseValidBidRequests[0].userId = { criteoId: 'sample-userid' }; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.have.nested.property('user.ext.eids'); expect(payload.user.ext.eids).to.be.an('array'); expect(payload.user.ext.eids.length).to.equal(1); }); it('should not set user ids when none present', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; }); it('should set imp.0.video.[w|h|placement] by default', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'imp.0.video.w')).to.equal(768); expect(deepAccess(payload, 'imp.0.video.h')).to.equal(432); expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(3); }); it('should update imp0.video.[w|h] when present in config', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); newBaseValidBidRequests[0].mediaTypes.video.playerSize = [1, 1]; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'imp.0.video.w')).to.equal(1); expect(deepAccess(payload, 'imp.0.video.h')).to.equal(1); }); it('should allow overriding any imp0.video key through params.video', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); newBaseValidBidRequests[0].params.video = { w: 2, h: 2, placement: 1, minduration: 15, maxduration: 30 }; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(payload, 'imp.0.video.w')).to.equal(2); expect(deepAccess(payload, 'imp.0.video.h')).to.equal(2); expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(1); expect(deepAccess(payload, 'imp.0.video.minduration')).to.equal(15); expect(deepAccess(payload, 'imp.0.video.maxduration')).to.equal(30); }); it('should not allow placing any non-OpenRTB 2.5 keys on imp.0.video through params.video', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); newBaseValidBidRequests[0].params.video = { 'true': true, 'testing': 'some', 123: {}, '': 'values' }; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(deepAccess(request, 'imp.0.video.true')).to.be.undefined; expect(deepAccess(payload, 'imp.0.video.testing')).to.be.undefined; expect(deepAccess(payload, 'imp.0.video.123')).to.be.undefined; expect(deepAccess(payload, 'imp.0.video.')).to.be.undefined; }); }); describe('interpretResponse', () => { const publicationName = 'bbprebid.dev'; const rendererCode = 'glorious_renderer'; const baseValidBid = { bidder: BB_CONSTANTS.BIDDER_CODE, params: { accountId: 123, publicationName: publicationName, rendererCode: rendererCode, connections: [ BB_CONSTANTS.BIDDER_CODE ], bluebillywig: {} }, mediaTypes: { video: { context: 'outstream' } } }; const baseValidBidRequests = [baseValidBid]; const validBidderRequest = { auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', auctionStart: 1585918458868, bidderCode: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', bids: [{ adUnitCode: 'ad-unit-test', auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', bidId: '1234ab567c89de0', bidRequestsCount: 1, bidder: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', params: baseValidBid.params, sizes: [[640, 480], [630, 360]], transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' }], start: 11585918458869, timeout: 3000 }; const validResponse = { id: 'a12abc345-67d8-9012-e345-6f78901a2b34', seatbid: [ { bid: [ { id: '1', impid: '1234ab567c89de0', price: 1, adm: '<?xml version="1.0" encoding="UTF-8"?>\r\n<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:noNamespaceSchemaLocation="vast.xsd"><Ad id="3707967"><InLine><AdSystem>BB Adserver</AdSystem><AdTitle><![CDATA[DO NOT DELETE - PREBID E2E TEST CLIP]]></AdTitle><Impression><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=it]]></Impression><Creatives><Creative><Linear><Duration>00:00:51</Duration><TrackingEvents><Tracking event="firstQuartile"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=25]]></Tracking><Tracking event="midpoint"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=50]]></Tracking><Tracking event="thirdQuartile"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=75]]></Tracking><Tracking event="complete"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=100]]></Tracking><Tracking event="start"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=st]]></Tracking></TrackingEvents><VideoClicks><ClickTracking><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xcl]]></ClickTracking><ClickThrough><![CDATA[]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery="progressive" type="video/mp4" bitrate="2000" height="720" width="1280"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358872305594.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="1600" height="432" width="768"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358872983333.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="400" height="288" width="512"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358873227458.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="800" height="432" width="768"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358873595994.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="3000" height="1080" width="1920"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358874138607.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>', adid: '67069817', adomain: [ 'bluebillywig.com' ], cid: '3535', crid: '67069817', w: 1, h: 1, publicationName: 'bbprebid', accountId: 123, ext: { prebid: { targeting: { hb_bidder: 'bluebillywig', hb_pb: '1.00', hb_size: '1x1' }, type: 'video' }, bidder: { prebid: { targeting: { hb_bidder: 'bluebillywig', hb_pb: '10.00', hb_size: '1x1' }, type: 'video', video: { duration: 51, primary_category: '' } }, bidder: { bluebillywig: { brand_id: 1, auction_id: 1, bid_ad_type: 1, creative_info: { video: { duration: 51, mimes: [ 'video/x-flv', 'video/mp4', 'video/webm' ] } } } } } } } ], seat: 'bluebillywig' } ], cur: 'USD', ext: { responsetimemillis: { bluebillywig: 0 }, tmaxrequest: 5000 } }; const serverResponse = { body: validResponse }; it('should build bid array', () => { const response = deepClone(serverResponse); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(1); }); it('should have all relevant fields', () => { const response = deepClone(serverResponse); const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); const bid = result[0]; // BB_HELPERS.transformRTBToPrebidProps expect(bid.cpm).to.equal(serverResponse.body.seatbid[0].bid[0].price); expect(bid.bidId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); expect(bid.requestId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); expect(bid.width).to.equal(serverResponse.body.seatbid[0].bid[0].w || BB_CONSTANTS.DEFAULT_WIDTH); expect(bid.height).to.equal(serverResponse.body.seatbid[0].bid[0].h || BB_CONSTANTS.DEFAULT_HEIGHT); expect(bid.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm); expect(bid.netRevenue).to.equal(BB_CONSTANTS.DEFAULT_NET_REVENUE); expect(bid.creativeId).to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(bid.currency).to.equal(serverResponse.body.cur); expect(bid.ttl).to.equal(BB_CONSTANTS.DEFAULT_TTL); expect(bid).to.have.property('meta'); expect(bid.meta).to.have.property('advertiserDomains'); expect(bid.meta.advertiserDomains[0]).to.equal('bluebillywig.com'); expect(bid.publicationName).to.equal(validBidderRequest.bids[0].params.publicationName); expect(bid.rendererCode).to.equal(validBidderRequest.bids[0].params.rendererCode); expect(bid.accountId).to.equal(validBidderRequest.bids[0].params.accountId); }); it('should not give anything when seatbid is an empty array', () => { const seatbidEmptyArray = deepClone(serverResponse); seatbidEmptyArray.body.seatbid = []; const response = seatbidEmptyArray; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); }); it('should not give anything when seatbid is missing', () => { const seatbidMissing = deepClone(serverResponse); delete seatbidMissing.body.seatbid; const response = seatbidMissing; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); }); const seatbidNotArrayResponse = deepClone(serverResponse); it('should not give anything when seatbid is not an array', () => { const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; for (const invalidValue of invalidValues) { seatbidNotArrayResponse.body.seatbid = invalidValue const response = deepClone(seatbidNotArrayResponse); // interpretResponse is destructive const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); } }); it('should not give anything when seatbid.bid is an empty array', () => { const seatbidBidEmpty = deepClone(serverResponse); seatbidBidEmpty.body.seatbid[0].bid = []; const response = seatbidBidEmpty; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); }); it('should not give anything when seatbid.bid is missing', () => { const seatbidBidMissing = deepClone(serverResponse); delete seatbidBidMissing.body.seatbid[0].bid; const response = seatbidBidMissing; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); }); it('should not give anything when seatbid.bid is not an array', () => { const seatbidBidNotArray = deepClone(serverResponse); const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; for (const invalidValue of invalidValues) { seatbidBidNotArray.body.seatbid[0].bid = invalidValue; const response = deepClone(seatbidBidNotArray); // interpretResponse is destructive const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(result.length).to.equal(0); } }); it('should take default width and height when w/h not present', () => { const bidSizesMissing = deepClone(serverResponse); delete bidSizesMissing.body.seatbid[0].bid[0].w; delete bidSizesMissing.body.seatbid[0].bid[0].h; const response = bidSizesMissing; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(deepAccess(result, '0.width')).to.equal(768); expect(deepAccess(result, '0.height')).to.equal(432); }); it('should take nurl value when adm not present', () => { const bidAdmMissing = deepClone(serverResponse); delete bidAdmMissing.body.seatbid[0].bid[0].adm; bidAdmMissing.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; const response = bidAdmMissing; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(deepAccess(result, '0.vastXml')).to.be.undefined; expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); }); it('should not take nurl value when adm present', () => { const bidAdmNurlPresent = deepClone(serverResponse); bidAdmNurlPresent.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; const response = bidAdmNurlPresent; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(deepAccess(result, '0.vastXml')).to.equal(bidAdmNurlPresent.body.seatbid[0].bid[0].adm); expect(deepAccess(result, '0.vastUrl')).to.be.undefined; }); it('should take ext.prebid.cache data when present, ignore ext.prebid.targeting and nurl', () => { const bidExtPrebidCache = deepClone(serverResponse); delete bidExtPrebidCache.body.seatbid[0].bid[0].adm; bidExtPrebidCache.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; bidExtPrebidCache.body.seatbid[0].bid[0].ext = { prebid: { cache: { vastXml: { url: 'https://bluebillywig.com', cacheId: '12345' } }, targeting: { hb_uuid: '23456', hb_cache_host: 'bluebillywig.com', hb_cache_path: '/cache' } } }; const response = bidExtPrebidCache; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); expect(deepAccess(result, '0.videoCacheKey')).to.equal('12345'); }); it('should take ext.prebid.targeting data when ext.prebid.cache not present, and ignore nurl', () => { const bidExtPrebidTargeting = deepClone(serverResponse); delete bidExtPrebidTargeting.body.seatbid[0].bid[0].adm; bidExtPrebidTargeting.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; bidExtPrebidTargeting.body.seatbid[0].bid[0].ext = { prebid: { targeting: { hb_uuid: '34567', hb_cache_host: 'bluebillywig.com', hb_cache_path: '/cache' } } }; const response = bidExtPrebidTargeting; const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const result = spec.interpretResponse(response, request); expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com/cache?uuid=34567'); expect(deepAccess(result, '0.videoCacheKey')).to.equal('34567'); }); }); describe('getUserSyncs', () => { const publicationName = 'bbprebid.dev'; const rendererCode = 'glorious_renderer'; const baseValidBid = { bidder: BB_CONSTANTS.BIDDER_CODE, params: { accountId: 123, publicationName: publicationName, rendererCode: rendererCode, connections: [ BB_CONSTANTS.BIDDER_CODE ], bluebillywig: {} }, mediaTypes: { video: { context: 'outstream' } } }; const validBidRequests = [baseValidBid]; const validBidderRequest = { auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', auctionStart: 1585918458868, bidderCode: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', bids: [{ adUnitCode: 'ad-unit-test', auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', bidId: '1234ab567c89de0', bidRequestsCount: 1, bidder: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', params: baseValidBid.params, sizes: [[768, 432], [640, 480], [630, 360]], transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' }], start: 11585918458869, timeout: 3000 }; const validResponse = { id: 'a12abc345-67d8-9012-e345-6f78901a2b34', seatbid: [ { bid: [ { id: '1', impid: '1234ab567c89de0', price: 1, adm: '<?xml version="1.0" encoding="UTF-8"?>\r\n<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:noNamespaceSchemaLocation="vast.xsd"><Ad id="3707967"><InLine><AdSystem>BB Adserver</AdSystem><AdTitle><![CDATA[DO NOT DELETE - PREBID E2E TEST CLIP]]></AdTitle><Impression><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=it]]></Impression><Creatives><Creative><Linear><Duration>00:00:51</Duration><TrackingEvents><Tracking event="firstQuartile"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=25]]></Tracking><Tracking event="midpoint"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=50]]></Tracking><Tracking event="thirdQuartile"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=75]]></Tracking><Tracking event="complete"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xpg&pct=100]]></Tracking><Tracking event="start"><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=st]]></Tracking></TrackingEvents><VideoClicks><ClickTracking><![CDATA[https://stats.bluebillywig.com?et=vasttracker&pd=&ct=DO NOT DELETE - PREBID E2E TEST CLIP&du=51&sot=on_demand&pm=external&sid=1eb6e953-360a-4655-9bc8-3378e691a077&pp=ovp&ts=1586359000&id=3707967&cid=3707967&pt=vasttracker&mt=vast&ev=xcl]]></ClickTracking><ClickThrough><![CDATA[]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery="progressive" type="video/mp4" bitrate="2000" height="720" width="1280"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358872305594.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="1600" height="432" width="768"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358872983333.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="400" height="288" width="512"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358873227458.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="800" height="432" width="768"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358873595994.mp4]]></MediaFile><MediaFile delivery="progressive" type="video/mp4" bitrate="3000" height="1080" width="1920"><![CDATA[https://d736l0py1kx0s.cloudfront.net/ovp/media/2020/04/08/asset-3707967-1586358874138607.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>', adid: '67069817', adomain: [ 'bluebillywig.com' ], cid: '3535', crid: '67069817', w: 1, h: 1, publicationName: 'bbprebid', accountId: 123, ext: { prebid: { targeting: { hb_bidder: 'bluebillywig', hb_pb: '1.00', hb_size: '1x1' }, type: 'video' }, bidder: { prebid: { targeting: { hb_bidder: 'bluebillywig', hb_pb: '10.00', hb_size: '1x1' }, type: 'video', video: { duration: 51, primary_category: '' } }, bidder: { bluebillywig: { brand_id: 1, auction_id: 1, bid_ad_type: 1, creative_info: { video: { duration: 51, mimes: [ 'video/x-flv', 'video/mp4', 'video/webm' ] } } } } } } } ], seat: 'bluebillywig' } ], cur: 'USD', ext: { responsetimemillis: { bluebillywig: 0 }, tmaxrequest: 5000 } }; const serverResponse = { body: validResponse }; const gdpr = { consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAA AAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', gdprApplies: true }; it('should return empty if no server response', function () { const result = spec.getUserSyncs({}, false, gdpr); expect(result).to.be.empty; }); it('should return empty if server response is empty', function () { const result = spec.getUserSyncs({}, [], gdpr); expect(result).to.be.empty; }); it('should return empty if iframeEnabled is not true', () => { const result = spec.getUserSyncs({iframeEnabled: false}, [serverResponse], gdpr); expect(result).to.be.empty; }); it('should append the various values if they exist', function() { // push data to syncStore spec.buildRequests(validBidRequests, validBidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, [serverResponse], gdpr); expect(result).to.not.be.empty; expect(result[0].url).to.include('gdpr=1'); expect(result[0].url).to.include(gdpr.consentString); expect(result[0].url).to.include('accountId=123'); expect(result[0].url).to.include(`bidders=${btoa(JSON.stringify(validBidRequests[0].params.connections))}`); expect(result[0].url).to.include('cb='); }); }); });