From 0d7b35d7cd84ce519aec1c15c8245616fdca86f5 Mon Sep 17 00:00:00 2001 From: optimatic58 <33465594+optimatic58@users.noreply.github.com> Date: Mon, 20 Nov 2017 16:53:57 -0500 Subject: [PATCH] + Add Optimatic Bid Adapter (#1837) * + Add Optimatic Bid Adapter * + cleaned up player log ajax call (as requested) + replaced "partnerId" with correct "placement" parameter name * + commit again --- modules/optimaticBidAdapter.js | 101 +++++++++++++ modules/optimaticBidAdapter.md | 30 ++++ test/spec/modules/optimaticBidAdapter_spec.js | 140 ++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 modules/optimaticBidAdapter.js create mode 100644 modules/optimaticBidAdapter.md create mode 100644 test/spec/modules/optimaticBidAdapter_spec.js diff --git a/modules/optimaticBidAdapter.js b/modules/optimaticBidAdapter.js new file mode 100644 index 00000000000..dee127c7414 --- /dev/null +++ b/modules/optimaticBidAdapter.js @@ -0,0 +1,101 @@ +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import { registerBidder } from 'src/adapters/bidderFactory'; +export const ENDPOINT = '//mg-bid.optimatic.com/adrequest/'; + +export const spec = { + code: 'optimatic', + + supportedMediaTypes: ['video'], + + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.placement && bid.params.bidfloor); + }, + + buildRequests: function(bids) { + return bids.map(bid => { + return { + method: 'POST', + url: ENDPOINT + bid.params.placement, + data: getData(bid), + options: {contentType: 'application/json'}, + bidRequest: bid + } + }) + }, + + 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.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, + vastXml: bid.adm, + width: size.width, + height: size.height, + mediaType: 'video', + currency: response.cur, + ttl: 300, + netRevenue: true + }; + return bidResponse; + } +}; + +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 getData (bid) { + let size = getSize(bid.sizes); + let loc = utils.getTopWindowLocation(); + let global = (window.top) ? window.top : window; + return { + id: utils.generateUUID(), + imp: [{ + id: '1', + bidfloor: bid.params.bidfloor, + video: { + mimes: ['video/mp4', 'video/ogg', 'video/webm', 'video/x-flv', 'application/javascript', 'application/x-shockwave-flash'], + width: size.width, + height: size.height + } + }], + site: { + id: '1', + domain: loc.host, + page: loc.href, + ref: utils.getTopWindowReferrer(), + publisher: { + id: '1' + } + }, + device: { + ua: global.navigator.userAgent, + ip: '127.0.0.1', + devicetype: 1 + } + }; +} + +config.setConfig({ usePrebidCache: true }); +registerBidder(spec); diff --git a/modules/optimaticBidAdapter.md b/modules/optimaticBidAdapter.md new file mode 100644 index 00000000000..edaa3da90f6 --- /dev/null +++ b/modules/optimaticBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Optimatic Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@optimatic.com +``` + +# Description + +Optimatic Bid Adapter Module connects to Optimatic Demand Sources for Video Ads + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[640,480]], // a video size + bids: [ + { + bidder: "optimatic", + params: { + placement: "2chy7Gc2eSQL", + bidfloor: 2.5 + } + } + ] + }, + ]; +``` diff --git a/test/spec/modules/optimaticBidAdapter_spec.js b/test/spec/modules/optimaticBidAdapter_spec.js new file mode 100644 index 00000000000..cbea5598627 --- /dev/null +++ b/test/spec/modules/optimaticBidAdapter_spec.js @@ -0,0 +1,140 @@ +import { expect } from 'chai'; +import { spec, ENDPOINT } from 'modules/optimaticBidAdapter'; +import * as utils from 'src/utils'; + +describe('OptimaticBidAdapter', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = { + bidder: 'optimatic', + params: { + placement: '2chy7Gc2eSQL', + bidfloor: 5.00 + }, + adUnitCode: 'adunit-code', + sizes: [ 640, 480 ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + 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 "bidfloor" param is missing', () => { + bidRequest.params = { + placement: '2chy7Gc2eSQL' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "placement" param is missing', () => { + bidRequest.params = { + bidfloor: 5.00 + }; + 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); + }); + + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).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(ENDPOINT + bidRequest.params.placement); + }); + + 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.width).to.equal(width); + expect(data.imp[0].video.height).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.width).to.equal(width); + expect(data.imp[0].video.height).to.equal(height); + }); + + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + bidRequest.sizes = `${width}x${height}`; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.width).to.equal(width); + expect(data.imp[0].video.height).to.equal(height); + }); + + it('must handle an empty bid size', () => { + bidRequest.sizes = []; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.width).to.equal(undefined); + expect(data.imp[0].video.height).to.equal(undefined); + }); + }); + + 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 "adm" is missing', () => { + const serverResponse = {seatbid: [{bid: [{price: 5.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', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.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: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + }); +});