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);
+ });
+ });
+});