diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js new file mode 100644 index 00000000000..a745c54e39c --- /dev/null +++ b/modules/smartxBidAdapter.js @@ -0,0 +1,400 @@ +import * as utils from '../src/utils.js'; +import { + Renderer +} from '../src/Renderer.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + VIDEO +} from '../src/mediaTypes.js'; +const BIDDER_CODE = 'smartx'; +const URL = 'https://bid.sxp.smartclip.net/bid/1000'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + /** + * Determines whether or not the given bid request is valid. + * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (bid && typeof bid.params !== 'object') { + utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + return false; + } + if (!utils.deepAccess(bid, 'mediaTypes.video')) { + utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + return false; + } + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + if (!playerSize || !utils.isArray(playerSize)) { + utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); + return false; + } + if (!utils.getBidIdParameter('tagId', bid.params)) { + utils.logError(BIDDER_CODE + ': tagId is not present in bidder params'); + return false; + } + if (!utils.getBidIdParameter('publisherId', bid.params)) { + utils.logError(BIDDER_CODE + ': publisherId is not present in bidder params'); + return false; + } + if (!utils.getBidIdParameter('siteId', bid.params)) { + utils.logError(BIDDER_CODE + ': siteId is not present in bidder params'); + return false; + } + if (!utils.getBidIdParameter('bidfloor', bid.params)) { + utils.logError(BIDDER_CODE + ': bidfloor is not present in bidder params'); + return false; + } + if (!utils.getBidIdParameter('bidfloorcur', bid.params)) { + utils.logError(BIDDER_CODE + ': bidfloorcur is not present in bidder params'); + return false; + } + if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + if (!utils.getBidIdParameter('outstream_options', bid.params)) { + utils.logError(BIDDER_CODE + ': outstream_options parameter is not defined'); + return false; + } + if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { + utils.logError(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration'); + return false; + } + if (!utils.getBidIdParameter('outstream_function', bid.params)) { + utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); + return true; + } + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + const page = bidderRequest.refererInfo.referer; + const isPageSecure = !!page.match(/^https:/) + + const smartxRequests = bidRequests.map(function (bid) { + const tagId = utils.getBidIdParameter('tagId', bid.params); + const publisherId = utils.getBidIdParameter('publisherId', bid.params); + const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params); + const siteId = utils.getBidIdParameter('siteId', bid.params); + const domain = utils.getBidIdParameter('domain', bid.params); + const cat = utils.getBidIdParameter('cat', bid.params); + let pubcid = null; + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const contentWidth = playerSize[0][0]; + const contentHeight = playerSize[0][1]; + const secure = +(isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0)); + const ext = { + sdk_name: 'Prebid 1+' + }; + const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; + const linearity = utils.getBidIdParameter('linearity', bid.params) || 1; + const minduration = utils.getBidIdParameter('minduration', bid.params) || 0; + const maxduration = utils.getBidIdParameter('maxduration', bid.params) || 500; + const startdelay = utils.getBidIdParameter('startdelay', bid.params) || 0; + const minbitrate = utils.getBidIdParameter('minbitrate', bid.params) || 0; + const maxbitrate = utils.getBidIdParameter('maxbitrate', bid.params) || 3500; + const delivery = utils.getBidIdParameter('delivery', bid.params) || [2]; + const pos = utils.getBidIdParameter('pos', bid.params) || 1; + const api = utils.getBidIdParameter('api', bid.params) || [2]; + const protocols = utils.getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; + var contextcustom = utils.deepAccess(bid, 'mediaTypes.video.context'); + var placement = 1; + + if (contextcustom === 'outstream') { + placement = 3; + } + + let smartxReq = { + id: bid.bidId, + secure: secure, + bidfloor: bidfloor, + bidfloorcur: bidfloorcur, + video: { + w: contentWidth, + h: contentHeight, + mimes: mimes, + linearity: linearity, + minduration: minduration, + maxduration: maxduration, + startdelay: startdelay, + protocols: protocols, + minbitrate: minbitrate, + maxbitrate: maxbitrate, + delivery: delivery, + pos: pos, + placement: placement, + api: api, + ext: ext + }, + tagid: tagId, + ext: { + 'smart.bidpricetype': 1 + } + }; + + if (bid.crumbs && bid.crumbs.pubcid) { + pubcid = bid.crumbs.pubcid; + } + + const language = navigator.language ? 'language' : 'userLanguage'; + const device = { + h: screen.height, + w: screen.width, + dnt: utils.getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent + }; + const at = utils.getBidIdParameter('at', bid.params) || 2; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const requestPayload = { + id: utils.generateUUID(), + imp: smartxReq, + site: { + id: siteId, + page: page, + cat: cat, + content: 'content', + domain: domain, + publisher: { + id: publisherId + } + }, + device: device, + at: at, + cur: cur + }; + const userExt = {}; + + // Add GDPR flag and consent string + if (bidderRequest && bidderRequest.gdprConsent) { + userExt.consent = bidderRequest.gdprConsent.consentString; + if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { + requestPayload.regs = { + ext: { + gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) + } + }; + } + } + + // Add common id if available + if (pubcid) { + userExt.fpc = pubcid; + } + + // Only add the user object if it's not empty + if (!utils.isEmpty(userExt)) { + requestPayload.user = { + ext: userExt + }; + } + + // Targeting + if (utils.getBidIdParameter('data', bid.params.user)) { + var targetingarr = []; + for (var i = 0; i < bid.params.user.data.length; i++) { + var isemq = (bid.params.user.data[i].name) || 'empty'; + if (isemq !== 'empty') { + var provider = bid.params.user.data[i].name; + var targetingstring = (bid.params.user.data[i].segment[0].value) || 'empty'; + targetingarr.push({ + id: provider, + name: provider, + segment: { + name: provider, + value: targetingstring, + } + }) + } + } + + requestPayload.user = { + ext: userExt, + data: targetingarr + } + } + + return { + method: 'POST', + url: URL, + data: requestPayload, + bidRequest: bidderRequest, + options: { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.3' + } + } + }; + }); + + return smartxRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidderRequest) { + const bidResponses = []; + const serverResponseBody = serverResponse.body; + if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) { + utils._each(serverResponseBody.seatbid, function (bids) { + utils._each(bids.bid, function (smartxBid) { + let currentBidRequest = {}; + for (let i in bidderRequest.bidRequest.bids) { + if (smartxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { + currentBidRequest = bidderRequest.bidRequest.bids[i]; + } + } + /** + * Make sure currency and price are the right ones + * TODO: what about the pre_market_bid partners sizes? + */ + utils._each(currentBidRequest.params.pre_market_bids, function (pmb) { + if (pmb.deal_id == smartxBid.id) { + smartxBid.price = pmb.price; + serverResponseBody.cur = pmb.currency; + } + }); + const bid = { + requestId: currentBidRequest.bidId, + currency: serverResponseBody.cur || 'USD', + cpm: smartxBid.price, + creativeId: smartxBid.crid || '', + ttl: 360, + netRevenue: true, + vastContent: smartxBid.adm, + vastXml: smartxBid.adm, + mediaType: VIDEO, + width: smartxBid.w, + height: smartxBid.h + }; + const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + if (context === 'outstream') { + const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); + const renderer = Renderer.install({ + id: 0, + url: '//', + config: { + adText: 'SmartX Outstream Video Ad via Prebid.js', + player_width: playersize[0][0], + player_height: playersize[0][1], + content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), + ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), + hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), + outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), + outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') + } + }); + try { + renderer.setRender(outstreamRender); + renderer.setEventHandlers({ + impression: function impression() { + return utils.logMessage('SmartX outstream video impression event'); + }, + loaded: function loaded() { + return utils.logMessage('SmartX outstream video loaded event'); + }, + ended: function ended() { + utils.logMessage('SmartX outstream renderer video event'); + } + }); + } catch (err) { + utils.logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); + } + bid.renderer = renderer; + } + bidResponses.push(bid); + }) + }); + } + return bidResponses; + } +} + +function createOutstreamScript(bid) { + // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); + const elementId = bid.adUnitCode; + // eslint-disable-next-line camelcase + var sc_smartIntxtStart; + // eslint-disable-next-line camelcase + var sc_smartIntxtNoad; + // eslint-disable-next-line camelcase + var sc_smartIntxtEnd; + var SmartPlay; + let smartPlayObj = { + minAdWidth: 290, + maxAdWidth: 900, + elementLocator: { + allowInViewport: false, + minimumElementWidth: 290, + scanPixelsBelowViewport: 800 + }, + onStartCallback: function (m, n) { + try { + sc_smartIntxtStart(n); + } catch (f) {} + }, + onCappedCallback: function (m, n) { + try { + sc_smartIntxtNoad(n); + } catch (f) {} + }, + onEndCallback: function (m, n) { + try { + sc_smartIntxtEnd(n); + } catch (f) {} + }, + debug: true + }; + smartPlayObj.adResponse = bid.vastContent; + const script = window.document.createElement('script'); + script.type = 'text/javascript'; + script.async = 'true'; + script.src = 'https://dco.smartclip.net/?plc=7777777'; + script.onload = script.onreadystatechange = function () { + var rs = this.readyState; + if (rs && rs != 'complete' && rs != 'loaded') return; + try { + SmartPlay(elementId, smartPlayObj); + } catch (e) { + utils.logError('error caught : ' + e); + } + }; + return script; +} + +function outstreamRender(bid) { + const script = createOutstreamScript(bid); + if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { + bid.renderer.config.outstream_function(bid, script); + } else { + try { + const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + if (slot && window.document.getElementById(slot)) { + window.document.getElementById(slot).appendChild(script); + } else { + window.document.getElementsByTagName('head')[0].appendChild(script); + } + } catch (err) { + utils.logError('[SMARTX][renderer] Error:' + err.message) + } + } +} +registerBidder(spec); diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md new file mode 100644 index 00000000000..a53af839e2b --- /dev/null +++ b/modules/smartxBidAdapter.md @@ -0,0 +1,159 @@ +# Overview + +``` +Module Name: smartclip Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adtech@smartclip.tv +``` + +# Description + +Connect to smartx for bids. + +This adapter requires setup and approval from the smartclip team. + +# Test Parameters - Use case #1 - Out-Stream example and default rendering options +``` + var adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 360] + } + }, + bids: [{ + bidder: 'smartx', + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: '11986', + siteId: '22860', + bidfloor: 0.3, + bidfloorcur: "EUR", + at: 2, + cur: ["EUR"], + outstream_options: { + slot: 'video1' + }, + } + }], + }]; +``` + +# Test Parameters - Use case #2 - Out-Stream with targeting example and default rendering options +``` + var adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 360] + } + }, + bids: [{ + bidder: 'smartx', + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: '11986', + siteId: '22860', + bidfloor: 0.3, + bidfloorcur: "EUR", + at: 2, + cur: ["EUR"], + outstream_options: { + slot: 'video1' + }, + user: { + data: [{ + id: 'emq', + name: 'emq', + segment: [{ + id: 'emq', + name: 'emq', + value: 'e0:k14:e24' + }] + }, { + id: 'gs', + name: 'gs', + segment: [{ + id: 'gs', + name: 'gs', + value: 'tone_of_voice_dislike:tone_of_voice_negative:gs_health' + }] + }] + } + } + }] + }]; +``` + +# Test Parameters - Use case #3 - In-Stream example and default rendering options +``` + var adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + bids: [{ + bidder: 'smartx', + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: '11986', + siteId: '22860', + bidfloor: 0.3, + bidfloorcur: "EUR", + at: 2, + cur: ["EUR"] + } + }], + }]; +``` + +# Test Parameters - Use case #4 - In-Stream with targeting example and default rendering options +``` + var adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + bids: [{ + bidder: 'smartx', + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: '11986', + siteId: '22860', + bidfloor: 0.3, + bidfloorcur: "EUR", + at: 2, + cur: ["EUR"], + user: { + data: [{ + id: 'emq', + name: 'emq', + segment: [{ + id: 'emq', + name: 'emq', + value: 'e0:k14:e24' + }] + }, + { + id: 'gs', + name: 'gs', + segment: [{ + id: 'gs', + name: 'gs', + value: 'tone_of_voice_dislike:tone_of_voice_negative:gs_health' + }] + } + ] + } + } + }], + }]; +``` \ No newline at end of file diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js new file mode 100644 index 00000000000..efc6abcc5fa --- /dev/null +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -0,0 +1,513 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/smartxBidAdapter.js'; + +describe('The smartx adapter', function () { + function getValidBidObject() { + return { + bidId: 123, + mediaTypes: { + video: { + // context: 'outstream', + playerSize: [ + ['640', '360'] + ] + } + }, + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: '__name__', + siteId: '__name__', + bidfloor: 0.3, + bidfloorcur: 'EUR', + // user: { + // data: '' + // } + // outstream_options: { + // slot: 'yourelementid' + // } + } + + }; + }; + + describe('isBidRequestValid', function () { + var bid; + + beforeEach(function () { + bid = getValidBidObject(); + }); + + it('should fail validation if the bid isn\'t defined or not an object', function () { + var result = spec.isBidRequestValid(); + + expect(result).to.equal(false); + + result = spec.isBidRequestValid('not an object'); + + expect(result).to.equal(false); + }); + + it('should succeed validation with all the right parameters', function () { + expect(spec.isBidRequestValid(getValidBidObject())).to.equal(true); + }); + + it('should succeed validation with mediaType and outstream_function or outstream_options', function () { + bid.mediaType = 'video'; + bid.params.outstream_function = 'outstream_func'; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + + delete bid.params.outstream_function; + bid.params.outstream_options = { + slot: 'yourelementid' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should succeed with ad_unit outstream and outstream function set', function () { + bid.params.ad_unit = 'outstream'; + bid.params.outstream_function = function () {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should succeed with mediaTypes_video_context outstream, options set for outstream and slot provided', function () { + bid.mediaTypes.video.context = 'outstream'; + bid.params.outstream_options = { + slot: 'yourelementid' + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should fail without video', function () { + delete bid.mediaTypes.video; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without playerSize', function () { + delete bid.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without tagId', function () { + delete bid.params.tagId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without publisherId', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without siteId', function () { + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without bidfloor', function () { + delete bid.params.bidfloor; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without bidfloorcur', function () { + delete bid.params.bidfloorcur; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail with context outstream but no options set for outstream', function () { + bid.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail with context outstream, options set for outstream but no slot provided', function () { + bid.mediaTypes.video.context = 'outstream'; + bid.params.outstream_options = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should succeed with context outstream, options set for outstream but no outstream_function is set', function () { + bid.mediaTypes.video.context = 'outstream'; + bid.params.outstream_options = { + slot: 'yourelementid' + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + var bid, + bidRequestObj; + + beforeEach(function () { + bid = getValidBidObject(); + bidRequestObj = { + refererInfo: { + referer: 'prebid.js' + } + }; + }); + + it('should build a very basic request', function () { + var request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.sxp.smartclip.net/bid/1000'); + expect(request.bidRequest).to.equal(bidRequestObj); + expect(request.data.imp.id).to.match(/\d+/); + expect(request.data.imp.secure).to.equal(0); + + expect(request.data.imp.video).to.deep.equal({ + ext: { + sdk_name: 'Prebid 1+' + }, + h: '360', + w: '640', + mimes: [ + 'application/javascript', 'video/mp4', 'video/webm' + ], + api: [2], + delivery: [2], + linearity: 1, + maxbitrate: 3500, + maxduration: 500, + minbitrate: 0, + minduration: 0, + protocols: [ + 2, 3, 5, 6 + ], + startdelay: 0, + placement: 1, + pos: 1 + }); + + expect(request.data.site).to.deep.equal({ + content: 'content', + id: '__name__', + page: 'prebid.js', + cat: '', + domain: '', + publisher: { + id: '__name__' + } + }); + }); + + it('should change request parameters based on options sent', function () { + var request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.ext).to.deep.equal({ + sdk_name: 'Prebid 1+' + }); + + expect(request.data.imp.video).to.contain({ + placement: 1 + }); + + bid.mediaTypes.video.context = 'outstream'; + + bid.params = { + outstream_options: { + foo: 'bar' + }, + outstream_function: '987', + mimes: 'foo', + linearity: 2, + minduration: 5, + maxduration: 10, + startdelay: 1, + minbitrate: 50, + maxbitrate: 500, + delivery: [1], + pos: 2, + api: [1], + protocols: [ + 2, 3, 5 + ], + bidfloor: 55, + bidfloorcur: 'foo', + at: 1, + cur: ['FOO'] + }; + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.ext).to.deep.equal({ + sdk_name: 'Prebid 1+' + }); + + expect(request.data.imp.video).to.contain({ + minduration: 5, + maxduration: 10 + }); + + expect(request.data.imp.video.startdelay).to.equal(1); + + expect(request.data.imp.video).to.contain({ + placement: 3 + }); + + expect(request.data.imp.bidfloor).to.equal(55); + + expect(request.data.imp.bidfloorcur).to.equal('foo'); + + expect(request.data.imp.video.linearity).to.equal(2); + + expect(request.data.imp.video.minbitrate).to.equal(50); + + expect(request.data.imp.video.maxbitrate).to.equal(500); + }); + + it('should pass GDPR params', function () { + var request; + + bidRequestObj.gdprConsent = { + gdprApplies: true, + consentString: 'foo' + } + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.user.ext.consent).to.be.an('string'); + expect(request.data.user.ext.consent).to.equal('foo'); + }); + + it('should pass emq params', function () { + var request; + + bid.params.user = { + data: [{ + id: 'emq', + name: 'emq', + segment: [{ + name: 'emq', + value: 'foo' + }] + }] + } + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.user.data).to.deep.equal([{ + id: 'emq', + name: 'emq', + segment: { + name: 'emq', + value: 'foo' + } + }]); + }); + + it('should pass crumbs params', function () { + var request; + + bid.crumbs = { + pubcid: 'pubcid_1' + }; + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.user.ext).to.contain({ + fpc: 'pubcid_1' + }); + }); + + it('should pass linearity params', function () { + var request; + + bid.params.linearity = 3 + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.linearity).to.equal(3); + }); + + it('should pass min and max duration params', function () { + var request; + + bid.params.minduration = 3 + bid.params.maxduration = 15 + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.imp.video.minduration).to.equal(3); + expect(request.data.imp.video.maxduration).to.equal(15); + }); + }); + + describe('interpretResponse', function () { + var serverResponse, bidderRequestObj; + + beforeEach(function () { + bidderRequestObj = { + bidRequest: { + bids: [{ + mediaTypes: { + video: { + playerSize: [ + ['400', '300'] + ] + } + }, + bidId: 123, + params: { + player_width: 400, + player_height: 300, + content_page_url: 'prebid.js', + ad_mute: 1, + outstream_options: { + foo: 'bar' + }, + outstream_function: 'function', + } + }, { + mediaTypes: { + video: { + playerSize: [ + ['200', '100'] + ] + } + }, + bidId: 124, + params: { + player_width: 200, + player_height: 100, + content_page_url: 'prebid.js', + ad_mute: 1, + outstream_options: { + foo: 'bar' + }, + outstream_function: 'function' + } + }] + } + }; + + serverResponse = { + body: { + id: 12345, + seatbid: [{ + bid: [{ + impid: 123, + cur: 'USD', + price: 12, + adomain: ['abc.com'], + crid: 321, + w: 400, + h: 300, + ext: { + slot: 'slot123' + } + }, { + impid: 124, + cur: 'USD', + price: 13, + adomain: ['def.com'], + crid: 654, + w: 200, + h: 100, + ext: { + slot: 'slot124' + } + }] + }] + } + }; + }); + + it('should return an array of bid responses', function () { + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + expect(responses).to.be.an('array').with.length(2); + expect(responses[0].requestId).to.equal(123); + expect(responses[0].currency).to.equal('USD'); + expect(responses[0].cpm).to.equal(12); + expect(responses[0].creativeId).to.equal(321); + expect(responses[0].ttl).to.equal(360); + expect(responses[0].netRevenue).to.equal(true); + expect(responses[0].mediaType).to.equal('video'); + expect(responses[0].width).to.equal(400); + expect(responses[0].height).to.equal(300); + expect(responses[1].requestId).to.equal(124); + expect(responses[1].currency).to.equal('USD'); + expect(responses[1].cpm).to.equal(13); + expect(responses[1].creativeId).to.equal(654); + expect(responses[1].ttl).to.equal(360); + expect(responses[1].netRevenue).to.equal(true); + expect(responses[1].mediaType).to.equal('video'); + expect(responses[1].width).to.equal(200); + expect(responses[1].height).to.equal(100); + }); + }); + + describe('oustreamRender', function () { + var serverResponse, bidderRequestObj; + + beforeEach(function () { + bidderRequestObj = { + bidRequest: { + bids: [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ + ['400', '300'] + ] + } + }, + bidId: 123, + params: { + player_width: 400, + player_height: 300, + content_page_url: 'prebid.js', + ad_mute: 1, + outstream_options: { + slot: 'slot123' + }, + outstream_function: 'function', + } + }] + } + }; + + serverResponse = { + body: { + id: 12345, + seatbid: [{ + bid: [{ + impid: 123, + cur: 'USD', + price: 12, + adomain: ['abc.com'], + crid: 321, + w: 400, + h: 300, + ext: { + slot: 'slot123' + } + }] + }] + } + }; + }); + + it('should attempt to insert the EASI script', function () { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + appendChild: sinon.stub().callsFake(function (script) { + scriptTag = script + }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + responses[0].renderer.render(responses[0]); + + expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777777'); + + window.document.getElementById.restore(); + }); + }); +})