From 3116d7f557587b2cf981b384e961e8dd831eea43 Mon Sep 17 00:00:00 2001 From: ankur-modi <38654685+ankur-modi@users.noreply.github.com> Date: Sat, 5 May 2018 00:01:11 +0530 Subject: [PATCH] One Video Adapter (#2445) * One Video Adapter * adding more test cases --- modules/oneVideoBidAdapter.js | 189 +++++++++++++++++++ modules/oneVideoBidAdapter.md | 47 +++++ test/spec/modules/oneVideoBidAdapter_spec.js | 135 +++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 modules/oneVideoBidAdapter.js create mode 100755 modules/oneVideoBidAdapter.md create mode 100644 test/spec/modules/oneVideoBidAdapter_spec.js diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js new file mode 100644 index 00000000000..c56669b8b0c --- /dev/null +++ b/modules/oneVideoBidAdapter.js @@ -0,0 +1,189 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'oneVideo'; +export const spec = { + code: 'oneVideo', + ENDPOINT: '//ads.adaptv.advertising.com/rtb/openrtb?ext_id=', + SYNC_ENDPOINT1: 'https://cm.g.doubleclick.net/pixel?google_nid=adaptv_dbm&google_cm&google_sc', + SYNC_ENDPOINT2: 'https://pr-bh.ybp.yahoo.com/sync/adaptv_ortb/{combo_uid}', + SYNC_ENDPOINT3: 'https://sync-tm.everesttech.net/upi/pid/m7y5t93k?redir=https%3A%2F%2Fsync.adap.tv%2Fsync%3Ftype%3Dgif%26key%3Dtubemogul%26uid%3D%24%7BUSER_ID%7D', + SYNC_ENDPOINT4: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', + supportedMediaTypes: ['video'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + // Video validations + if (typeof bid.params.video === 'undefined' || typeof bid.params.video.playerWidth === 'undefined' || typeof bid.params.video.playerHeight == 'undefined' || typeof bid.params.video.mimes == 'undefined') { + return false; + } + + // Pub Id validation + if (typeof bid.params.pubId === 'undefined') { + return false; + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bids) { + return bids.map(bid => { + return { + method: 'POST', + url: location.protocol + spec.ENDPOINT + bid.params.pubId, + data: getRequestData(bid), + options: {contentType: 'application/json'}, + bidRequest: bid + } + }) + }, + /** + * 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(response, { bidRequest }) { + let bid; + let size; + let bidResponse; + try { + response = response.body; + bid = response.seatbid[0].bid[0]; + } catch (e) { + response = null; + } + if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { + utils.logWarn(`No valid bids from ${spec.code} bidder`); + return []; + } + size = getSize(bidRequest.sizes); + bidResponse = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: bid.price, + creativeId: bid.id, + width: size.width, + height: size.height, + mediaType: 'video', + currency: response.cur, + ttl: 100, + netRevenue: true + }; + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } else if (bid.adm) { + bidResponse.vastXml = bid.adm; + } + return bidResponse; + }, + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: spec.SYNC_ENDPOINT1 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT2 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT3 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT4 + }]; + } + } +}; + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; + return { + width: parseInt(width, 10) || undefined, + height: parseInt(height, 10) || undefined + }; +} + +function getRequestData(bid) { + let loc = utils.getTopWindowLocation(); + let global = (window.top) ? window.top : window; + let page = (bid.params.site.page) ? (bid.params.site.page) : (loc.href); + let ref = (bid.params.site.referrer) ? bid.params.site.referrer : utils.getTopWindowReferrer(); + let bidData = { + id: utils.generateUUID(), + at: 2, + cur: bid.cur || 'USD', + imp: [{ + id: '1', + secure: isSecure(), + bidfloor: bid.params.bidfloor, + video: { + mimes: bid.params.video.mimes, + w: bid.params.video.playerWidth, + h: bid.params.video.playerHeight, + linearity: 1, + protocols: bid.params.video.protocols || [2, 5] + } + }], + site: { + page: page, + ref: ref + }, + device: { + ua: global.navigator.userAgent + }, + tmax: 200 + }; + + if (bid.params.video.maxbitrate) { + bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate + } + if (bid.params.video.maxduration) { + bidData.imp[0].video.maxduration = bid.params.video.maxduration + } + if (bid.params.video.minduration) { + bidData.imp[0].video.minduration = bid.params.video.minduration + } + if (bid.params.video.api) { + bidData.imp[0].video.api = bid.params.video.api + } + if (bid.params.video.delivery) { + bidData.imp[0].video.delivery = bid.params.video.delivery + } + if (bid.params.video.position) { + bidData.imp[0].video.pos = bid.params.video.position + } + if (bid.params.site.id) { + bidData.site.id = bid.params.site.id + } + return bidData; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md new file mode 100755 index 00000000000..96399221315 --- /dev/null +++ b/modules/oneVideoBidAdapter.md @@ -0,0 +1,47 @@ +# Overview + +**Module Name**: One Video Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: ankur.modi@oath.com + +# Description + +Connects to One Video demand source to fetch bids. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'video1', + sizes: [640,480], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2] + }, + site: { + id: 1, + page: 'http://abhi12345.com', + referrer: 'http://abhi12345.com' + }, + pubId: 'brxd' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js new file mode 100644 index 00000000000..3d7bba417f9 --- /dev/null +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -0,0 +1,135 @@ +import { expect } from 'chai'; +import { spec } from 'modules/oneVideoBidAdapter'; +import * as utils from 'src/utils'; + +describe('OneVideoBidAdapter', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = { + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2] + }, + site: { + id: 1, + page: 'https://news.yahoo.com/portfolios', + referrer: 'http://www.yahoo.com' + }, + pubId: 'brxd' + } + }; + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when the "video" param is missing', () => { + bidRequest.params = { + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "pubId" param is missing', () => { + bidRequest.params = { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', () => { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + it('should create a POST request for every bid', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(location.protocol + spec.ENDPOINT + bidRequest.params.pubId); + }); + + it('should attach the bid request object', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].bidRequest).to.equal(bidRequest); + }); + + it('should attach request data', () => { + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + bidRequest.sizes = [[ width, height ]]; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + }); + }); + + describe('spec.interpretResponse', () => { + it('should return no bids if the response is not valid', () => { + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', () => { + const serverResponse = {seatbid: [{bid: [{price: 6.01}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', () => { + const serverResponse = {seatbid: [{bid: [{adm: ''}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response with just "adm"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + let o = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].id, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 100, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + }); +});