diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js new file mode 100644 index 00000000000..92d7ece4adb --- /dev/null +++ b/modules/gamoshiBidAdapter.js @@ -0,0 +1,293 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {config} from '../src/config'; +import {Renderer} from '../src/Renderer'; +import {BANNER, VIDEO} from '../src/mediaTypes'; + +const ENDPOINTS = { + 'gamoshi': 'https://rtb.gamoshi.io' +}; + +const DEFAULT_TTL = 360; + +export const helper = { + getTopFrame: function () { + try { + return window.top === window ? 1 : 0; + } catch (e) { + } + return 0; + }, + startsWith: function (str, search) { + return str.substr(0, search.length) === search; + }, + getTopWindowDomain: function (url) { + const domainStart = url.indexOf('://') + '://'.length; + return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + }, + + getMediaType: function (bid) { + if (bid.ext) { + if (bid.ext.media_type) { + return bid.ext.media_type.toLowerCase(); + } else if (bid.ext.vast_url) { + return VIDEO; + } else { + return BANNER; + } + } + return BANNER; + } +}; + +export const spec = { + code: 'gamoshi', + aliases: ['gambid', 'cleanmedia', '9MediaOnline'], + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!bid.params.supplyPartnerId && utils.isStr(bid.params.supplyPartnerId) && + (!bid.params['rtbEndpoint'] || utils.isStr(bid.params['rtbEndpoint'])) && + (!bid.params.bidfloor || utils.isNumber(bid.params.bidfloor)) && + (!bid.params['adpos'] || utils.isNumber(bid.params['adpos'])) && + (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) && + (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; + const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi']; + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); + let url = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + + const rtbBidRequest = { + id: auctionId, + site: { + domain: helper.getTopWindowDomain(url), + page: url, + ref: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + imp: [], + ext: {}, + user: { + ext: {} + } + }; + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + rtbBidRequest.ext.gdpr_consent = { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + rtbBidRequest.regs = { + ext: { + gdpr: gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + rtbBidRequest.user = { + ext: { + consent: gdprConsent.consentString + } + } + } + const imp = { + id: transactionId, + instl: params.instl === 1 ? 1 : 0, + tagid: adUnitCode, + bidfloor: params.bidfloor || 0, + bidfloorcur: 'USD', + secure: 1 + }; + + const hasFavoredMediaType = + params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType); + + if (!mediaTypes || mediaTypes.banner) { + if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { + const bannerImp = Object.assign({}, imp, { + banner: { + w: sizes.length ? sizes[0][0] : 300, + h: sizes.length ? sizes[0][1] : 250, + pos: params.pos || 0, + topframe: utils.inIframe() ? 0 : 1 + } + }); + rtbBidRequest.imp.push(bannerImp); + } + } + + if (mediaTypes && mediaTypes.video) { + if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { + const playerSize = mediaTypes.video.playerSize || sizes; + const videoImp = Object.assign({}, imp, { + video: { + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + ext: { + context: mediaTypes.video.context + } + } + }); + + if (utils.isArray(playerSize[0])) { + videoImp.video.w = playerSize[0][0]; + videoImp.video.h = playerSize[0][1]; + } else if (utils.isNumber(playerSize[0])) { + videoImp.video.w = playerSize[0]; + videoImp.video.h = playerSize[1]; + } else { + videoImp.video.w = 300; + videoImp.video.h = 250; + } + + rtbBidRequest.imp.push(videoImp); + } + } + + let eids = []; + if (bidRequest && bidRequest.userId) { + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + } + if (eids.length > 0) { + rtbBidRequest.user.ext.eids = eids; + } + + if (rtbBidRequest.imp.length === 0) { + return; + } + + 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), []); + let outBids = []; + + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: DEFAULT_TTL, + creativeId: bid.crid || bid.adid, + netRevenue: true, + currency: bid.cur || response.cur, + mediaType: helper.getMediaType(bid) + }; + + if (utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { + if (outBid.mediaType === BANNER) { + outBids.push(Object.assign({}, outBid, {ad: bid.adm})); + } else if (outBid.mediaType === VIDEO) { + const context = utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context'); + outBids.push(Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined + })); + } + } + }); + return outBids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + const syncs = []; + const gdprApplies = gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') ? gdprConsent.gdprApplies : false; + const suffix = gdprApplies ? 'gc=' + encodeURIComponent(gdprConsent.consentString) : 'gc=missing'; + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { + bidResponse.ext['utrk'].forEach(pixel => { + const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, 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 => { + const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + }); + } + }); + } + } + }); + return syncs; + } +}; + +function newRenderer(bidRequest, bid, rendererOptions = {}) { + const renderer = Renderer.install({ + url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || 'https://s.gamoshi.io/video/latest/renderer.js', + config: rendererOptions, + loaded: false, + }); + try { + renderer.setRender(renderOutstream); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + const unitId = bid.adUnitCode + '/' + bid.adId; + window['GamoshiPlayer'].renderAd({ + id: unitId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: document.getElementById(bid.adUnitCode), + width: bid.width, + height: bid.height, + events: { + ALL_ADS_COMPLETED: () => window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) + }, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml + }); + }); +} + +function addExternalUserId(eids, value, source, rtiPartner) { + if (utils.isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + ext: { + rtiPartner + } + }] + }); + } +} + +registerBidder(spec); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js new file mode 100644 index 00000000000..a0e569a905f --- /dev/null +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -0,0 +1,549 @@ +import {expect} from 'chai'; +import {spec, helper} from 'modules/gamoshiBidAdapter'; +import * as utils from 'src/utils'; +import {newBidder} from '../../../src/adapters/bidderFactory'; + +const supplyPartnerId = '123'; +const adapter = newBidder(spec); +describe('GamoshiAdapter', function () { + describe('Get top Frame', function () { + it('check if you are in the top frame', function () { + expect(helper.getTopFrame()).to.equal(0); + }); + it('verify domain parsing', function () { + expect(helper.getTopWindowDomain('http://www.domain.com')).to.equal('www.domain.com'); + }); + }); + describe('Is String start with search ', function () { + it('check if a string started with', function () { + expect(helper.startsWith('gamoshi.com', 'gamo')).to.equal(true); + }); + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should validate supply-partner ID', function () { + 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', function () { + 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', function () { + 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', function () { + 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', function () { + 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', function () { + const bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + refererInfo: {referer: 'http://examplereferer.com'}, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + } + }; + it('returns an array', function () { + let response; + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + response = spec.buildRequests([bidRequest], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'}); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'}); + response = spec.buildRequests([adUnit1, adUnit2], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('targets correct endpoint', function () { + let response; + response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://rtb\\.gamoshi\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + expect(response.data.id).to.equal(bidRequest.auctionId); + const bidRequestWithEndpoint = utils.deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gamoshi.io/a12'; + response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; + expect(response.url).to.match(new RegExp(`^https://rtb2\\.gamoshi\\.io/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + }); + + it('builds request correctly', function () { + let bidRequest2 = utils.deepClone(bidRequest); + bidRequest2.refererInfo.referer = 'http://www.test.com/page.html'; + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://www.test.com/page.html'); + 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 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl); + const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0]; + expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); + }); + + it('builds request banner object correctly', function () { + let response; + const bidRequestWithBanner = utils.deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithBanner], bidRequest)[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 = utils.deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [[302, 252]] + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); + expect(response.data.imp[0].video.pos).to.equal(0); + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [302, 252] + } + }; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[1]); + + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly with context', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + context: 'instream' + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal(null); + }); + + it('builds request video object correctly with multi-dimensions size array', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [[304, 254], [305, 255]], + context: 'instream' + }; + + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal(null); + }); + + it('builds request with gdpr consent', function () { + let response = spec.buildRequests([bidRequest], bidRequest)[0]; + + expect(response.data.ext.gdpr_consent).to.not.equal(null).and.not.equal(undefined); + expect(response.data.ext).to.have.property('gdpr_consent'); + expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string'); + expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); + + expect(response.data.regs.ext.gdpr).to.not.equal(null).and.not.equal(undefined); + expect(response.data.user.ext.consent).to.equal('some string'); + }); + + it('build request with ID5 Id', function () { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.id5id = 'id5-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'ext': { + 'rtiPartner': 'ID5ID' + } + }] + }]); + }); + + it('build request with unified Id', function () { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.tdid = 'tdid-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'tdid-user-id', + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + }); + + describe('interpretResponse', () => { + const bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + const videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + const rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'utrk': [ + {'type': 'iframe', 'url': '//rtb.gamoshi.io/user/sync/1'}, + {'type': 'image', 'url': '//rtb.gamoshi.io/user/sync/2'} + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': '', + 'adomain': ['aaa.com'], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'vast_url': 'http://my.vast.com', + 'utrk': [ + {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'} + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': ' ', + 'adomain': ['bbb.com'], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'utrk': [ + {'type': 'image', 'url': '//p.partner2.io/user/sync/1'} + ] + } + } + ] + } + ] + }; + + const TTL = 360; + + it('returns an empty array on missing response', function () { + 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', function () { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); + expect(ad0.ttl).to.equal(TTL); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); + expect(ad0.vastXml).to.be.an('undefined'); + expect(ad0.vastUrl).to.be.an('undefined'); + }); + + it('aggregates video bids from all seat bids', function () { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + 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(TTL); + 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(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); + }); + + it('aggregates user-sync pixels', function () { + 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.utrk[0].type); + expect(response[0].url).to.equal(rtbResponse.ext.utrk[0].url + '?gc=missing'); + expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); + expect(response[1].url).to.equal(rtbResponse.ext.utrk[1].url + '?gc=missing'); + expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type); + expect(response[2].url).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing'); + expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type); + expect(response[3].url).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing'); + }); + + it('supports configuring outstream renderers', function () { + const videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675' + }] + } + }; + const videoRequest = utils.deepClone(videoBidRequest); + videoRequest.mediaTypes.video.context = 'outstream'; + const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest}); + expect(result[0].renderer).to.not.equal(undefined); + }); + + it('validates in/existing of gdpr consent', function () { + let videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675' + }] + } + }; + let gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=consent%20string'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=missing'); + + videoResponse.ext.utrk[0].url = 'https://rtb.gamoshi.io/pix/1275/scm'; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?gc=missing'); + }); + }); +});