UNPKG

mk9-prebid

Version:

Header Bidding Management Library

1,792 lines (1,539 loc) 51.6 kB
import { expect } from 'chai'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { spec } from 'modules/33acrossBidAdapter.js'; function validateBuiltServerRequest(builtReq, expectedReq) { expect(builtReq.url).to.equal(expectedReq.url); expect(builtReq.options).to.deep.equal(expectedReq.options); expect(JSON.parse(builtReq.data)).to.deep.equal( JSON.parse(expectedReq.data) ) } describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; let bidRequests; let sandbox; function TtxRequestBuilder() { const ttxRequest = { imp: [{}], site: { id: SITE_ID }, id: 'b1', regs: { ext: { gdpr: 0 } }, ext: { ttx: { prebidStartedAt: 1, caller: [{ 'name': 'prebidjs', 'version': '$prebid.version$' }] } } }; this.withBanner = () => { Object.assign(ttxRequest.imp[0], { banner: { format: [ { w: 300, h: 250 }, { w: 728, h: 90 } ], ext: { ttx: { viewability: { amount: 100 } } } } }); return this; }; this.withBannerSizes = this.withSizes = sizes => { Object.assign(ttxRequest.imp[0].banner, { format: sizes }); return this; }; this.withVideo = (params = {}) => { Object.assign(ttxRequest.imp[0], { video: { w: 300, h: 250, placement: 2, ...params } }); return this; }; this.withViewability = (viewability, format = 'banner') => { Object.assign(ttxRequest.imp[0][format], { ext: { ttx: { viewability } } }); return this; }; this.withProduct = (prod = PRODUCT_ID) => { Object.assign(ttxRequest.imp[0], { ext: { ttx: { prod } } }); return this; }; this.withGdprConsent = (consent, gdpr) => { Object.assign(ttxRequest, { user: { ext: { consent } } }); Object.assign(ttxRequest, { regs: { ext: Object.assign( {}, ttxRequest.regs.ext, { gdpr } ) } }); return this; }; this.withUspConsent = (consent) => { Object.assign(ttxRequest, { regs: { ext: Object.assign( {}, ttxRequest.regs.ext, { us_privacy: consent } ) } }); return this; }; this.withSite = site => { Object.assign(ttxRequest, { site }); return this; }; this.withPageUrl = pageUrl => { Object.assign(ttxRequest.site, { page: pageUrl }); return this; }; this.withSchain = schain => { Object.assign(ttxRequest, { source: { ext: { schain } } }); return this; }; this.withFloors = this.withFormatFloors = (mediaType, floors) => { switch (mediaType) { case 'banner': const format = ttxRequest.imp[0].banner.format.map((fm, i) => { return Object.assign(fm, { ext: { ttx: { bidfloors: [ floors[i] ] } } }) }); ttxRequest.imp[0].banner.format = format; break; case 'video': Object.assign(ttxRequest.imp[0].video, { ext: { ttx: { bidfloors: floors } } }); break; } return this; }; this.withUserIds = (eids) => { Object.assign(ttxRequest, { user: { ext: { eids } } }); return this; } this.build = () => ttxRequest; } function ServerRequestBuilder() { const serverRequest = { 'method': 'POST', 'url': `${END_POINT}?guid=${SITE_ID}`, 'data': null, 'options': { 'contentType': 'text/plain', 'withCredentials': true } }; this.withData = data => { serverRequest['data'] = JSON.stringify(data); return this; }; this.withUrl = url => { serverRequest['url'] = url; return this; }; this.withOptions = options => { serverRequest['options'] = options; return this; }; this.build = () => serverRequest; } function BidRequestsBuilder() { const bidRequests = [ { bidId: 'b1', bidder: '33across', bidderRequestId: 'b1a', params: { siteId: SITE_ID, productId: PRODUCT_ID }, adUnitCode: 'div-id', auctionId: 'r1', mediaTypes: {}, transactionId: 't1' } ]; this.withBanner = () => { bidRequests[0].mediaTypes.banner = { sizes: [ [300, 250], [728, 90] ] }; return this; }; this.withProduct = (prod) => { bidRequests[0].params.productId = prod; return this; }; this.withVideo = (params) => { bidRequests[0].mediaTypes.video = { playerSize: [[300, 250]], context: 'outstream', ...params }; return this; } this.withUserIds = (eids) => { bidRequests[0].userIdAsEids = eids; return this; }; this.build = () => bidRequests; } beforeEach(function() { element = { x: 0, y: 0, width: 0, height: 0, getBoundingClientRect: () => { return { width: element.width, height: element.height, left: element.x, top: element.y, right: element.x + element.width, bottom: element.y + element.height }; } }; win = { document: { visibilityState: 'visible' }, innerWidth: 800, innerHeight: 600 }; bidRequests = ( new BidRequestsBuilder() .withBanner() .build() ); sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); sandbox.stub(document, 'getElementById').withArgs('div-id').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); afterEach(function() { sandbox.restore(); }); describe('isBidRequestValid:', function() { context('basic validation', function() { it('returns true for valid guid values', function() { // NOTE: We ignore whitespace at the start and end since // in our experience these are common typos const validGUIDs = [ `${SITE_ID}`, `${SITE_ID} `, ` ${SITE_ID}`, ` ${SITE_ID} ` ]; validGUIDs.forEach((siteId) => { const bid = { bidder: '33across', params: { siteId } }; expect(spec.isBidRequestValid(bid)).to.be.true; }); }); it('returns false for invalid guid values', function() { const invalidGUIDs = [ undefined, 'siab' ]; invalidGUIDs.forEach((siteId) => { const bid = { bidder: '33across', params: { siteId } }; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); }); context('banner validation', function() { it('returns true when banner mediaType does not exist', function() { const bid = { bidder: '33across', params: { siteId: 'cxBE0qjUir6iopaKkGJozW' } }; expect(spec.isBidRequestValid(bid)).to.be.true; }); it('returns true when banner sizes are defined', function() { const bid = { bidder: '33across', mediaTypes: { banner: { sizes: [[250, 300]] } }, params: { siteId: 'cxBE0qjUir6iopaKkGJozW' } }; expect(spec.isBidRequestValid(bid)).to.be.true; }); it('returns false when banner sizes are invalid', function() { const invalidSizes = [ undefined, '16:9', 300, 'foo' ]; invalidSizes.forEach((sizes) => { const bid = { bidder: '33across', mediaTypes: { banner: { sizes } }, params: { siteId: 'cxBE0qjUir6iopaKkGJozW' } }; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); }); context('video validation', function() { beforeEach(function() { // Basic Valid BidRequest this.bid = { bidder: '33across', mediaTypes: { video: { playerSize: [[300, 50]], context: 'outstream', mimes: ['foo', 'bar'], protocols: [1, 2] } }, params: { siteId: `${SITE_ID}` } }; }); it('returns true when video mediaType does not exist', function() { const bid = { bidder: '33across', params: { siteId: `${SITE_ID}` } }; expect(spec.isBidRequestValid(bid)).to.be.true; }); it('returns true when valid video mediaType is defined', function() { expect(spec.isBidRequestValid(this.bid)).to.be.true; }); it('returns false when video context is not defined', function() { delete this.bid.mediaTypes.video.context; expect(spec.isBidRequestValid(this.bid)).to.be.false; }); it('returns false when video playserSize is invalid', function() { const invalidSizes = [ undefined, '16:9', 300, 'foo' ]; invalidSizes.forEach((playerSize) => { this.bid.mediaTypes.video.playerSize = playerSize; expect(spec.isBidRequestValid(this.bid)).to.be.false; }); }); it('returns false when video mimes is invalid', function() { const invalidMimes = [ undefined, 'foo', 1, [] ] invalidMimes.forEach((mimes) => { this.bid.mediaTypes.video.mimes = mimes; expect(spec.isBidRequestValid(this.bid)).to.be.false; }) }); it('returns false when video protocols is invalid', function() { const invalidMimes = [ undefined, 'foo', 1, [] ] invalidMimes.forEach((protocols) => { this.bid.mediaTypes.video.protocols = protocols; expect(spec.isBidRequestValid(this.bid)).to.be.false; }) }); it('returns false when video placement is invalid', function() { const invalidPlacement = [ [], '1', {}, 'foo' ]; invalidPlacement.forEach((placement) => { this.bid.mediaTypes.video.placement = placement; expect(spec.isBidRequestValid(this.bid)).to.be.false; }); }); it('returns false when video startdelay is invalid for instream context', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'instream', protocols: [1, 2], mimes: ['foo', 'bar']}) .build() ); const invalidStartdelay = [ [], '1', {}, 'foo' ]; invalidStartdelay.forEach((startdelay) => { bidRequests[0].mediaTypes.video.startdelay = startdelay; expect(spec.isBidRequestValid(bidRequests[0])).to.be.false; }); }); it('returns true when video startdelay is invalid for outstream context', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'outstream', protocols: [1, 2], mimes: ['foo', 'bar']}) .build() ); const invalidStartdelay = [ [], '1', {}, 'foo' ]; invalidStartdelay.forEach((startdelay) => { bidRequests[0].mediaTypes.video.startdelay = startdelay; expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; }); }); }) }); describe('buildRequests:', function() { context('when element is fully in view', function() { it('returns 100', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withViewability({amount: 100}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { width: 600, height: 400 }); const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when element is out of view', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when element is partially in view', function() { it('returns percentage', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withViewability({amount: 75}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { width: 800, height: 800 }); const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when width or height of the element is zero', function() { it('try to use alternative values', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withSizes([{ w: 800, h: 2400 }]) .withViewability({amount: 25}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when nested iframes', function() { it('returns \'nm\'', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withViewability({amount: spec.NON_MEASURABLE}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { width: 600, height: 400 }); utils.getWindowTop.restore(); utils.getWindowSelf.restore(); sandbox.stub(utils, 'getWindowTop').returns({}); sandbox.stub(utils, 'getWindowSelf').returns(win); const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when tab is inactive', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); Object.assign(element, { width: 600, height: 400 }); utils.getWindowTop.restore(); win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when gdpr consent data exists', function() { let bidderRequest; beforeEach(function() { bidderRequest = { gdprConsent: { consentString: 'foobarMyPreference', gdprApplies: true } } }); it('returns corresponding server requests with gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with gdpr consent data', function() { sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when gdpr consent data does not exist', function() { let bidderRequest; beforeEach(function() { bidderRequest = {}; }); it('returns corresponding server requests with default gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default gdpr consent data', function() { sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when us_privacy consent data exists', function() { let bidderRequest; beforeEach(function() { bidderRequest = { uspConsent: 'foo' } }); it('returns corresponding server requests with us_privacy consent data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with us_privacy consent data', function() { sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when us_privacy consent data does not exist', function() { let bidderRequest; beforeEach(function() { bidderRequest = {}; }); it('returns corresponding server requests with default us_privacy data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default us_privacy consent data', function() { sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when referer value is available', function() { it('returns corresponding server requests with site.page set', function() { const bidderRequest = { refererInfo: { referer: 'http://foo.com/bar' } }; const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withPageUrl('http://foo.com/bar') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when referer value is not available', function() { it('returns corresponding server requests without site.page set', function() { const bidderRequest = { refererInfo: {} }; const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when there is schain object in the bidRequest', function() { it('builds request with schain info in source', function() { const schainValues = [ { 'ver': '1.0', 'complete': 1, 'nodes': [ { 'asi': 'bidderA.com', 'sid': '00001', 'hp': 1 } ] }, { 'ver': '1.0', 'complete': 1, }, { 'ver': '1.0', 'complete': 1, 'nodes': [] }, { 'ver': '1.0', 'complete': '1', 'nodes': [ { 'asi': 'bidderA.com', 'sid': '00001', 'hp': 1 } ] } ]; schainValues.forEach((schain) => { bidRequests[0].schain = schain; const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withSchain(schain) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); }); context('when there no schain object is passed', function() { it('does not set source field', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when price floor module is not enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when price floor module is enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest if there is no floor', function() { bidRequests[0].getFloor = () => ({}); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('sets bidfloors in ttxRequest if there is a floor', function() { bidRequests[0].getFloor = ({size, currency, mediaType}) => { const floor = (size[0] === 300 && size[1] === 250) ? 1.0 : 0.10 return ( { floor, currency: 'USD' } ); }; const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withFormatFloors('banner', [ 1.0, 0.10 ]) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when mediaType has video only and context is instream', function() { it('builds instream request with default params', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'instream'}) .build() ); const ttxRequest = new TtxRequestBuilder() .withVideo() .withProduct('instream') .build(); ttxRequest.imp[0].video.placement = 1; ttxRequest.imp[0].video.startdelay = 0; const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds instream request with params passed', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'instream', startdelay: -2}) .build() ); const ttxRequest = new TtxRequestBuilder() .withVideo({startdelay: -2, placement: 1}) .withProduct('instream') .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); context('when mediaType has video only and context is outstream', function() { it('builds siab request with video only with default params', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'outstream'}) .build() ); const ttxRequest = new TtxRequestBuilder() .withVideo() .withProduct('siab') .build(); ttxRequest.imp[0].video.placement = 2; const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with video params passed', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) .build() ); const ttxRequest = new TtxRequestBuilder() .withVideo({placement: 3, playbackmethod: [2]}) .withProduct('siab') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when mediaType has banner only', function() { it('builds default siab request', function() { const bidRequests = ( new BidRequestsBuilder() .withBanner() .build() ); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct('siab') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds default inview request when product is set as such', function() { const bidRequests = ( new BidRequestsBuilder() .withBanner() .withProduct('inview') .build() ); const ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct('inview') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when mediaType has banner and video', function() { it('builds siab request with banner and outstream video', function() { const bidRequests = ( new BidRequestsBuilder() .withBanner() .withVideo({context: 'outstream'}) .build() ); const ttxRequest = new TtxRequestBuilder() .withBanner() .withVideo() .withProduct('siab') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('builds siab request with banner and outstream video even when context is instream', function() { const bidRequests = ( new BidRequestsBuilder() .withBanner() .withVideo({context: 'instream'}) .build() ); const ttxRequest = new TtxRequestBuilder() .withBanner() .withVideo() .withProduct('siab') .build(); ttxRequest.imp[0].video.placement = 2; const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); context('when price floor module is enabled for video in bidRequest', function() { it('does not set any bidfloors in video if there is no floor', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'outstream'}) .build() ); bidRequests[0].getFloor = () => ({}); const ttxRequest = new TtxRequestBuilder() .withVideo() .withProduct() .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); it('sets bidfloors in video if there is a floor', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({context: 'outstream'}) .build() ); bidRequests[0].getFloor = ({size, currency, mediaType}) => { const floor = (mediaType === 'video') ? 1.0 : 0.10 return ( { floor, currency: 'USD' } ); }; const ttxRequest = new TtxRequestBuilder() .withVideo() .withProduct() .withFloors('video', [ 1.0 ]) .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); context('when user ID data exists as userIdAsEids Array in bidRequest', function() { it('passes userIds in eids field in ORTB request', function() { const eids = [ { 'source': 'x-device-vendor-x.com', 'uids': [ { 'id': 'yyy', 'atype': 1 }, { 'id': 'zzz', 'atype': 1 }, { 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', 'atype': 2 } ], 'ext': { 'foo': 'bar' } } ]; const bidRequests = ( new BidRequestsBuilder() .withUserIds(eids) .build() ); const ttxRequest = new TtxRequestBuilder() .withUserIds(eids) .withProduct() .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); it('does not validate eids ORTB', function() { const eids = [1, 2, 3]; const bidRequests = ( new BidRequestsBuilder() .withUserIds(eids) .build() ); const ttxRequest = new TtxRequestBuilder() .withUserIds(eids) .withProduct() .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); context('when user IDs do not exist under the userIdAsEids field in bidRequest as a non-empty Array', function() { it('does not pass user IDs in the bidRequest ORTB', function() { const eidsScenarios = [ 'foo', [], {foo: 1} ]; eidsScenarios.forEach((eids) => { const bidRequests = ( new BidRequestsBuilder() .withUserIds(eids) .build() ); bidRequests.userId = { 'vendorx': { 'source': 'x-device-vendor-x.com', 'uids': [ { 'id': 'yyy', 'atype': 1 }, { 'id': 'zzz', 'atype': 1 }, { 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', 'atype': 2 } ], 'ext': { 'foo': 'bar' } } }; const ttxRequest = new TtxRequestBuilder() .withProduct() .build(); const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); }); }); }); describe('interpretResponse', function() { let ttxRequest, serverRequest; beforeEach(function() { ttxRequest = new TtxRequestBuilder() .withBanner() .withProduct() .withSite({ id: SITE_ID, page: 'https://test-url.com' }) .build(); serverRequest = new ServerRequestBuilder() .withUrl('https://staging-ssc.33across.com/api/v1/hb') .withData(ttxRequest) .withOptions({ contentType: 'text/plain', withCredentials: false }) .build(); }); context('when exactly one bid is returned', function() { it('interprets and returns the single banner bid response', function() { const serverResponse = { cur: 'USD', ext: {}, id: 'b1', seatbid: [ { bid: [{ id: '1', adm: '<html><h3>I am an ad</h3></html>', crid: 1, h: 250, w: 300, price: 0.0938, adomain: ['advertiserdomain.com'] }] } ] }; const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, ad: '<html><h3>I am an ad</h3></html>', ttl: 60, creativeId: 1, mediaType: 'banner', currency: 'USD', netRevenue: true, meta: { advertiserDomains: ['advertiserdomain.com'] } }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); it('interprets and returns the single video bid response', function() { const videoBid = '<VAST version="3.0"><Ad></Ad></VAST>'; const serverResponse = { cur: 'USD', ext: {}, id: 'b1', seatbid: [ { bid: [{ id: '1', adm: videoBid, ext: { ttx: { mediaType: 'video', vastType: 'xml' } }, crid: 1, h: 250, w: 300, price: 0.0938, adomain: ['advertiserdomain.com'] }] } ] }; const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, ad: videoBid, ttl: 60, creativeId: 1, mediaType: 'video', currency: 'USD', netRevenue: true, vastXml: videoBid, meta: { advertiserDomains: ['advertiserdomain.com'] } }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); context('when the list of advertiser domains for block list checking is empty', function() { it('doesn\'t include the empty list in the interpreted response', function() { const serverResponse = { cur: 'USD', ext: {}, id: 'b1', seatbid: [ { bid: [{ id: '1', adm: '<html><h3>I am an ad</h3></html>', crid: 1, h: 250, w: 300, price: 0.0938, adomain: [] // Empty list }] } ] }; // Bid response below doesn't contain meta.advertiserDomains const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, ad: '<html><h3>I am an ad</h3></html>', ttl: 60, creativeId: 1, mediaType: 'banner', currency: 'USD', netRevenue: true }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); }); }); context('when no bids are returned', function() { it('interprets and returns empty array', function() { const serverResponse = { cur: 'USD', ext: {}, id: 'b1', seatbid: [] }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([]); }); }); context('when more than one bids are returned', function() { it('interprets and returns the the first bid of the first seatbid', function() { const serverResponse = { cur: 'USD', ext: {}, id: 'b1', seatbid: [ { bid: [{ id: '1', adm: '<html><h3>I am an ad</h3></html>', crid: 1, h: 250, w: 300, price: 0.0940 }, { id: '2', adm: '<html><h3>I am an ad</h3></html>', crid: 2, h: 250, w: 300, price: 0.0938 } ] }, { bid: [{ id: '3', adm: '<html><h3>I am an ad</h3></html>', crid: 3, h: 250, w: 300, price: 0.0938 }] } ] }; const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, cpm: 0.0940, width: 300, height: 250, ad: '<html><h3>I am an ad</h3></html>', ttl: 60, creativeId: 1, mediaType: 'banner', currency: 'USD', netRevenue: true }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); }); }); describe('getUserSyncs', function() { let syncs; beforeEach(function() { syncs = [ { type: 'iframe', url: 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb&id=id1' }, { type: 'iframe', url: 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb&id=id2' }, ]; bidRequests = [ { bidId: 'b1', bidder: '33across', bidderRequestId: 'b1a', params: { siteId: 'id1', productId: 'foo' }, adUnitCode: 'div-id', auctionId: 'r1', mediaTypes: { banner: { sizes: [ [300, 250] ] } }, transactionId: 't1' }, { bidId: 'b2', bidder: '33across', bidderRequestId: 'b2a', params: { siteId: 'id2', productId: 'foo' }, adUnitCode: 'div-id', auctionId: 'r1', mediaTypes: { banner: { sizes: [ [300, 250] ] } }, transactionId: 't2' } ]; }); context('when iframe is not enabled', function() { it('returns empty sync array', function() { const syncOptions = {}; spec.buildRequests(bidRequests); expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); }); }); context('when iframe is enabled', function() { let syncOptions; beforeEach(function() { syncOptions = { iframeEnabled: true }; }); context('when there is no gdpr consent data', function() { it('returns sync urls with undefined consent string as param', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, undefined); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined` } ] expect(syncResults).to.deep.equal(expectedSyncs); }) }); context('when gdpr applies but there is no consent string', function() { it('returns sync urls with undefined consent string as param and gdpr=1', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true}); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=1` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=1` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); context('when gdpr applies and there is consent string', function() { it('returns sync urls with gdpr_consent=consent string as param and gdpr=1', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true, consentString: 'consent123A'}); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=1` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=1` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); context('when gdpr does not apply and there is no consent string', function() { it('returns sync urls with undefined consent string as param and gdpr=0', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false}); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=0` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=undefined&us_privacy=undefined&gdpr=0` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); context('when gdpr is unknown and there is consent string', function() { it('returns sync urls with only consent string as param', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, {consentString: 'consent123A'}); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); context('when gdpr does not apply and there is consent string (yikes!)', function() { it('returns sync urls with consent string as param and gdpr=0', function() { spec.buildRequests(bidRequests); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false, consentString: 'consent123A'}); const expectedSyncs = [ { type: 'iframe', url: `${syncs[0].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=0` }, { type: 'iframe', url: `${syncs[1].url}&gdpr_consent=consent123A&us_privacy=undefined&gdpr=0` } ]; expect(syncResults).to.deep.equal(expectedSyncs); }); }); context('when there is no usPrivacy