diff --git a/modules/gambidBidAdapter.js b/modules/gambidBidAdapter.js new file mode 100644 index 00000000000..bde378f8d26 --- /dev/null +++ b/modules/gambidBidAdapter.js @@ -0,0 +1,168 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; + +function getTopFrame() { + try { + return window.top === window ? 1 : 0; + } catch (e) { + return 0; + } +} + +function getTopWindowUrl() { + try { + return window.top.location.href; + } catch (e) { + utils.logMessage('Failed obtaining top window\'s URL: ', e); + try { + return window.location.href; + } catch (e) { + utils.logMessage('Failed obtaining current window\'s URL: ', e); + return ''; + } + } +} + +function getTopWindowDomain() { + const url = getTopWindowUrl(); + const domainStart = url.indexOf('://') + '://'.length; + return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); +} + +function getTopWindowReferer() { + try { + return window.top.document.referrer; + } catch (e) { + utils.logMessage('Failed obtaining top window\'s referrer: ', e); + try { + return window.document.referrer; + } catch (e) { + utils.logMessage('Failed obtaining current window\'s referrer: ', e); + return ''; + } + } +} + +export const spec = { + code: 'gambid', + aliases: [], + supportedMediaTypes: [ 'banner', 'video' ], + + isBidRequestValid: function(bid) { + return !!bid.params.supplyPartnerId && typeof bid.params.supplyPartnerId === 'string' && + (typeof bid.params[ 'rtbEndpoint' ] === 'undefined' || typeof bid.params[ 'rtbEndpoint' ] === 'string') && + (typeof bid.params.bidfloor === 'undefined' || typeof bid.params.bidfloor === 'number') && + (typeof bid.params[ 'adpos' ] === 'undefined' || typeof bid.params[ 'adpos' ] === 'number') && + (typeof bid.params[ 'protocols' ] === 'undefined' || Array.isArray(bid.params[ 'protocols' ])) && + (typeof bid.params.instl === 'undefined' || bid.params.instl === 0 || bid.params.instl === 1); + }, + + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + const { adUnitCode, auctionId, mediaTypes, params, sizes, transactionId } = bidRequest; + const baseEndpoint = params[ 'rtbEndpoint' ] || 'https://rtb.gambid.io'; + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&bidder=prebid&` + params.query || ''; + const rtbBidRequest = { + 'id': auctionId, + 'site': { + 'domain': getTopWindowDomain(), + 'page': config.getConfig('pageUrl') || utils.getTopWindowUrl(), + 'ref': getTopWindowReferer() + }, + 'device': { + 'ua': navigator.userAgent + }, + 'imp': [] + }; + + const imp = { + 'id': transactionId, + 'instl': params.instl === 1 ? 1 : 0, + 'tagid': adUnitCode, + 'bidfloor': params.bidfloor || 0, + 'bidfloorcur': 'USD', + 'secure': getTopWindowUrl().toLowerCase().startsWith('http://') ? 0 : 1 + }; + + if (!mediaTypes || mediaTypes.banner) { + imp.banner = { + w: sizes.length ? sizes[ 0 ][ 0 ] : 300, + h: sizes.length ? sizes[ 0 ][ 1 ] : 250, + pos: params.pos || 0, + topframe: getTopFrame() + }; + } else if (mediaTypes && mediaTypes.video) { + imp.video = { + w: sizes.length ? sizes[ 0 ][ 0 ] : 300, + h: sizes.length ? sizes[ 0 ][ 1 ] : 250, + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + topframe: getTopFrame() + }; + } else { + return; + } + rtbBidRequest.imp.push(imp); + return { method: 'POST', url: rtbEndpoint, data: rtbBidRequest, bidRequest }; + }); + }, + + interpretResponse: function(serverResponse, bidRequest) { + const response = serverResponse && serverResponse.body; + if (!response) { + utils.logError('empty response'); + return []; + } + + const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + const outBids = []; + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 60 * 10, + creativeId: bid.crid, + netRevenue: true, + currency: bid.cur || response.cur + }; + if (!bidRequest.bidRequest.mediaTypes || bidRequest.bidRequest.mediaTypes.banner) { + outBids.push(Object.assign({}, outBid, { ad: bid.adm })); + } else if ( bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video) { + outBids.push(Object.assign({}, outBid, { vastXml: bid.adm })); + } + }); + return outBids; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext[ 'utrk' ])) { + bidResponse.ext[ 'utrk' ].forEach(pixel => syncs.push({ type: pixel.type, url: pixel.url })); + } + if (Array.isArray(bidResponse.seatbid)) { + bidResponse.seatbid.forEach(seatBid => { + if (Array.isArray(seatBid.bid)) { + seatBid.bid.forEach(bid => { + if (bid.ext && Array.isArray(bid.ext[ 'utrk' ])) { + bid.ext[ 'utrk' ].forEach(pixel => syncs.push({ type: pixel.type, url: pixel.url })); + } + }); + } + }); + } + } + }); + return syncs; + }, + + onTimeout: function(data) { + utils.logWarn('Gambid request timed out.', data); + } +}; +registerBidder(spec); diff --git a/modules/gambidBidAdapter.md b/modules/gambidBidAdapter.md new file mode 100644 index 00000000000..ddf1c8f7851 --- /dev/null +++ b/modules/gambidBidAdapter.md @@ -0,0 +1,81 @@ +# Overview + +``` +Module Name: Gamoshi's Gambid Bid Adapter +Module Type: Bidder Adapter +Maintainer: arik@gamoshi.com +``` + +# Description + +Connects to Gamoshi's Gambid platform & exchange for bids. + +Gambid bid adapter supports Banner & Outstream Video. The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. + +# Test Parameters +``` +var adUnits = [ + + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'gambid', + params: { + + // ID of the supply partner you created in the Gambid dashboard + supplyPartnerId: '12345', + + // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here + rtbEndpoint: 'https://my.custom-whitelabel-domain.io', + + // OPTIONAL: custom bid floor + bidfloor: 0.03, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + instl: 0 + } + }] + }, + + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [ { + bidder: 'gambid', + params: { + + // ID of the supply partner you created in the Gambid dashboard + supplyPartnerId: '12345', + + // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here + rtbEndpoint: 'https://my.custom-whitelabel-domain.io', + + // OPTIONAL: custom bid floor + bidfloor: 0.03, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + instl: 0 + } + }] + } +]; +``` diff --git a/test/spec/modules/gambidBidAdapter_spec.js b/test/spec/modules/gambidBidAdapter_spec.js new file mode 100644 index 00000000000..e6cc77b3bad --- /dev/null +++ b/test/spec/modules/gambidBidAdapter_spec.js @@ -0,0 +1,331 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gambidBidAdapter'; +import { deepClone } from 'src/utils'; + +const supplyPartnerId = '123'; + +describe('GambidAdapter', () => { + describe('isBidRequestValid', () => { + it('should validate supply-partner ID', () => { + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); + }); + it('should validate RTB endpoint', () => { + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // RTB endpoint has a default + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', rtbEndpoint: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', rtbEndpoint: 'https://some.url.com' } })).to.equal(true); + }); + it('should validate bid floor', () => { + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // bidfloor has a default + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', bidfloor: '123' } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', bidfloor: 0.1 } })).to.equal(true); + }); + it('should validate adpos', () => { + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', adpos: '123' } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', adpos: 0.1 } })).to.equal(true); + }); + it('should validate instl', () => { + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: '123' } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: -1 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } })).to.equal(true); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } })).to.equal(true); + expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } })).to.equal(false); + }); + }); + describe('buildRequests', () => { + const bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'transactionId': 'a123456789' + }; + + it('returns an array', () => { + let response; + + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.buildRequests([ bidRequest ]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + + const adUnit1 = Object.assign({}, deepClone(bidRequest), { auctionId: '1', adUnitCode: 'a' }); + const adUnit2 = Object.assign({}, deepClone(bidRequest), { auctionId: '1', adUnitCode: 'b' }); + response = spec.buildRequests([adUnit1, adUnit2]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('targets correct endpoint', () => { + let response; + + response = spec.buildRequests([ bidRequest ])[ 0 ]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://rtb\\.gambid\\.io/r/${supplyPartnerId}/bidr\\?bidder=prebid&_cb=\\d+$`, 'g')); + expect(response.data.id).to.equal(bidRequest.auctionId); + + const bidRequestWithEndpoint = deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gambid.io/a12'; + response = spec.buildRequests([ bidRequestWithEndpoint ])[ 0 ]; + expect(response.url).to.match(new RegExp(`^https://rtb2\\.gambid\\.io/a12/r/${supplyPartnerId}/bidr\\?bidder=prebid&_cb=\\d+$`, 'g')); + }); + + it('builds request correctly', () => { + let response; + response = spec.buildRequests([ bidRequest ])[ 0 ]; + expect(response.data.site.domain).to.match(new RegExp(`^localhost:\\d+$`)); + expect(response.data.site.page).to.match(new RegExp(`^http://localhost:\\d+/$`)); + expect(response.data.site.ref).to.equal(''); + expect(response.data.imp.length).to.equal(1); + expect(response.data.imp[ 0 ].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[ 0 ].instl).to.equal(0); + expect(response.data.imp[ 0 ].tagid).to.equal(bidRequest.adUnitCode); + expect(response.data.imp[ 0 ].bidfloor).to.equal(0); + expect(response.data.imp[ 0 ].bidfloorcur).to.equal('USD'); + + const bidRequestWithInstlEquals1 = deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests([ bidRequestWithInstlEquals1 ])[ 0 ]; + expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals1.params.instl); + + const bidRequestWithInstlEquals0 = deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests([ bidRequestWithInstlEquals0 ])[ 0 ]; + expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + + const bidRequestWithBidfloorEquals1 = deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([ bidRequestWithBidfloorEquals1 ])[ 0 ]; + expect(response.data.imp[ 0 ].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); + }); + + it('builds request banner object correctly', () => { + let response; + + const bidRequestWithBanner = deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [ [ 300, 250 ], [ 120, 600 ] ] + } + }; + + response = spec.buildRequests([ bidRequestWithBanner ])[ 0 ]; + expect(response.data.imp[ 0 ].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 0 ]); + expect(response.data.imp[ 0 ].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 1 ]); + expect(response.data.imp[ 0 ].banner.pos).to.equal(0); + expect(response.data.imp[ 0 ].banner.topframe).to.equal(0); + + const bidRequestWithPosEquals1 = deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; + expect(response.data.imp[ 0 ].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly', () => { + let response; + + const bidRequestWithVideo = deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + sizes: [ [ 300, 250 ], [ 120, 600 ] ] + } + }; + + response = spec.buildRequests([ bidRequestWithVideo ])[ 0 ]; + expect(response.data.imp[ 0 ].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 0 ]); + expect(response.data.imp[ 0 ].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 1 ]); + expect(response.data.imp[ 0 ].video.pos).to.equal(0); + expect(response.data.imp[ 0 ].video.topframe).to.equal(0); + + const bidRequestWithPosEquals1 = deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; + expect(response.data.imp[ 0 ].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + }); + describe('interpretResponse', () => { + const bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'transactionId': 'a123456789', + 'bidId': '111' + }; + const videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'transactionId': 'a123456789', + 'bidId': '111' + }; + const rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'pixels': [ + { 'type': 'iframe', 'url': '//p.gsh.io/user/sync/1' }, + { 'type': 'image', 'url': '//p.gsh.io/user/sync/2' } + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://p.gsh.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + 'adm': ' ', + 'adomain': [ 'aaa.com' ], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'pixels': [ + { 'type': 'iframe', 'url': '//p.partner1.io/user/sync/1' } + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://p.gsh.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + 'adm': ' ', + 'adomain': [ 'bbb.com' ], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'pixels': [ + { 'type': 'image', 'url': '//p.partner2.io/user/sync/1' } + ] + } + } + ] + } + ] + }; + it('returns an empty array on missing response', () => { + let response; + + response = spec.interpretResponse(undefined, { bidRequest: bannerBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + }); + it('aggregates banner bids from all seat bids', () => { + const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: bannerBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + + const ad0 = response[ 0 ], ad1 = response[ 1 ]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); + expect(ad0.ttl).to.equal(60 * 10); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); + expect(ad0.vastXml).to.be.an('undefined'); + + expect(ad1.requestId).to.equal(bannerBidRequest.bidId); + expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); + expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); + expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); + expect(ad1.ttl).to.equal(60 * 10); + expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); + expect(ad1.ad).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); + expect(ad1.vastXml).to.be.an('undefined'); + + // expect(ad1.ad).to.be.an('undefined'); + // expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); + }); + it('aggregates video bids from all seat bids', () => { + const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: videoBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + + const ad0 = response[ 0 ], ad1 = response[ 1 ]; + expect(ad0.requestId).to.equal(videoBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); + expect(ad0.ttl).to.equal(60 * 10); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.be.an('undefined'); + expect(ad0.vastXml).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); + + expect(ad1.requestId).to.equal(videoBidRequest.bidId); + expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); + expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); + expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); + expect(ad1.ttl).to.equal(60 * 10); + expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); + expect(ad1.ad).to.be.an('undefined'); + expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); + }); + it('aggregates user-sync pixels', () => { + const response = spec.getUserSyncs({}, [ { body: rtbResponse } ]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(4); + expect(response[ 0 ].type).to.equal(rtbResponse.ext.pixels[ 0 ].type); + expect(response[ 0 ].url).to.equal(rtbResponse.ext.pixels[ 0 ].url); + expect(response[ 1 ].type).to.equal(rtbResponse.ext.pixels[ 1 ].type); + expect(response[ 1 ].url).to.equal(rtbResponse.ext.pixels[ 1 ].url); + expect(response[ 2 ].type).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.pixels[ 0 ].type); + expect(response[ 2 ].url).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.pixels[ 0 ].url); + expect(response[ 3 ].type).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.pixels[ 0 ].type); + expect(response[ 3 ].url).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.pixels[ 0 ].url); + }); + }); +});