diff --git a/modules/vertamediaBidAdapter.js b/modules/vertamediaBidAdapter.js index d87ae56def1..81e2872d371 100644 --- a/modules/vertamediaBidAdapter.js +++ b/modules/vertamediaBidAdapter.js @@ -1,119 +1,121 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; - -const ENDPOINT = '//rtb.vertamedia.com/hb/'; - -function VertamediaAdapter() { - const baseAdapter = new Adapter('vertamedia'); - let bidRequest; - - baseAdapter.callBids = function (bidRequests) { - if (!bidRequests || !bidRequests.bids || bidRequests.bids.length === 0) { - return; - } - - var RTBDataParams = prepareAndSaveRTBRequestParams(bidRequests.bids[0]); - - if (!RTBDataParams) { - return; - } - - ajax(ENDPOINT, handleResponse, RTBDataParams, { - contentType: 'text/plain', - withCredentials: true, - method: 'GET' +import {registerBidder} from 'src/adapters/bidderFactory'; +import {VIDEO} from 'src/mediaTypes'; + +const URL = '//rtb.vertamedia.com/hb/'; +const BIDDER_CODE = 'vertamedia'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + isBidRequestValid: function (bid) { + return Boolean(bid && bid.params && bid.params.aid); + }, + + /** + * Make a server request from the list of BidRequests + * @param bidRequests + * @param bidderRequest + */ + buildRequests: function (bidRequests, bidderRequest) { + return bidRequests.map((bid) => { + return { + data: prepareRTBRequestParams(bid), + bidderRequest, + method: 'GET', + url: URL + } }); - }; - - function prepareAndSaveRTBRequestParams(bid) { - if (!bid || !bid.params || !bid.params.aid || !bid.placementCode) { - return; + }, + + /** + * Unpack the response from the server into a list of bids + * @param serverResponse + * @param bidderRequest + * @return {Bid[]} An array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + const isInvalidValidResp = !serverResponse || !serverResponse.bids || !serverResponse.bids.length; + let bids = []; + + if (isInvalidValidResp) { + let extMessage = serverResponse && serverResponse.ext && serverResponse.ext.message ? `: ${serverResponse.ext.message}` : ''; + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter ${extMessage}`; + + utils.logError(errorMessage); + + return bids; } - bidRequest = bid; - - let size = getSize(bid.sizes); - - bidRequest.width = size.width; - bidRequest.height = size.height; - - return { - aid: bid.params.aid, - w: size.width, - h: size.height, - domain: document.location.hostname - }; - } - - function getSize(requestSizes) { - const parsed = {}; - const size = utils.parseSizesInput(requestSizes)[0]; - - if (typeof size !== 'string') { - return parsed; - } - - let parsedSize = size.toUpperCase().split('X'); - - return { - width: parseInt(parsedSize[0], 10) || undefined, - height: parseInt(parsedSize[1], 10) || undefined - }; - } - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response) { - var parsed; - - try { - parsed = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - - if (!parsed || parsed.error || !parsed.bids || !parsed.bids.length) { - bidmanager.addBidResponse(bidRequest.placementCode, createBid(STATUS.NO_BID)); + serverResponse.bids.forEach(serverBid => { + if (serverBid.cpm !== 0) { + const bid = createBid(serverBid); + bids.push(bid); + } + }); - return; - } + return bids; + }, +}; + +/** + * Prepare all parameters for request + * @param bid {object} + * @returns {object} + */ +function prepareRTBRequestParams(bid) { + let size = getSize(bid.sizes); + + return { + domain: utils.getTopWindowLocation().hostname, + callbackId: bid.bidId, + aid: bid.params.aid, + h: size.height, + w: size.width + }; +} - bidmanager.addBidResponse(bidRequest.placementCode, createBid(STATUS.GOOD, parsed.bids[0])); +/** + * Prepare size for request + * @param requestSizes {array} + * @returns {object} bid The bid to validate + */ +function getSize(requestSizes) { + const size = utils.parseSizesInput(requestSizes)[0]; + const parsed = {}; + + if (typeof size !== 'string') { + return parsed; } - function createBid(status, tag) { - var bid = bidfactory.createBid(status, tag); + let parsedSize = size.toUpperCase().split('X'); - bid.code = baseAdapter.getBidderCode(); - bid.bidderCode = bidRequest.bidder; - - if (!tag || status !== STATUS.GOOD) { - return bid; - } - - bid.mediaType = 'video'; - bid.cpm = tag.cpm; - bid.creative_id = tag.cmpId; - bid.width = bidRequest.width; - bid.height = bidRequest.height; - bid.descriptionUrl = tag.url; - bid.vastUrl = tag.url; - - return bid; - } - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode - }); + return { + height: parseInt(parsedSize[1], 10) || undefined, + width: parseInt(parsedSize[0], 10) || undefined + }; } -adaptermanager.registerBidAdapter(new VertamediaAdapter(), 'vertamedia', { - supportedMediaTypes: ['video'] -}); +/** + * Configure new bid by response + * @param bidResponse {object} + * @returns {object} + */ +function createBid(bidResponse) { + return { + requestId: bidResponse.requestId, + descriptionUrl: bidResponse.url, + creativeId: bidResponse.cmpId, + vastUrl: bidResponse.vastUrl, + height: bidResponse.height, + currency: bidResponse.cur, + width: bidResponse.width, + cpm: bidResponse.cpm, + mediaType: 'video', + netRevenue: true, + ttl: 3600 + }; +} -module.exports = VertamediaAdapter; +registerBidder(spec); diff --git a/modules/vertamediaBidAdapter.md b/modules/vertamediaBidAdapter.md new file mode 100644 index 00000000000..b64e6b24214 --- /dev/null +++ b/modules/vertamediaBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +**Module Name**: VertaMedia Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: support@verta.media + +# Description + +Get access to multiple demand partners across VertaMedia AdExchange and maximize your yield with VertaMedia header bidding adapter. + +VertaMedia header bidding adapter connects with VertaMedia demand sources in order to fetch bids. +This adapter provides a solution for accessing Video demand + + +# Test Parameters +``` + var adUnits = [{ + code: 'div-test-div', + sizes: [[640, 480]], // ad size + bids: [{ + bidder: 'vertamedia', // adapter name + params: { + aid: 332842 + } + }] + }]; +``` diff --git a/test/spec/modules/vertamediaBidAdapter_spec.js b/test/spec/modules/vertamediaBidAdapter_spec.js index 11c29dafad0..4f04ea8a615 100644 --- a/test/spec/modules/vertamediaBidAdapter_spec.js +++ b/test/spec/modules/vertamediaBidAdapter_spec.js @@ -1,141 +1,112 @@ -import { expect } from 'chai'; -import Adapter from 'modules/vertamediaBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const ENDPOINT = 'http://rtb.vertamedia.com/hb/?aid=22489&w=640&h=480&domain=localhost'; +import {expect} from 'chai'; +import {spec} from 'modules/vertamediaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +const ENDPOINT = '//rtb.vertamedia.com/hb/'; const REQUEST = { - 'bidderCode': 'vertamedia', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + 'bidder': 'vertamedia', + 'params': { + 'aid': 12345 + }, 'bidderRequestId': '7101db09af0db2', - 'bids': [ - { - 'bidder': 'vertamedia', - 'params': { - aid: 22489, - placementId: '123456' - }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [640, 480], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6' - } - ], - 'start': 1469479810130 + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [640, 480] }; -var RESPONSE = { - 'source': { - 'aid': 22489, - 'pubId': 18016, - 'sid': '0' - }, - 'bids': [ - { - 'cmpId': 9541, - 'cpm': 4.5, - 'url': 'http://rtb.vertamedia.com/vast?adid=BFDB9CC0038AD918', - 'cur': 'USD' - } + +const serverResponse = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'vastUrl': 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', + 'descriptionUrl': '44F2AEB9BFC881B3', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9 + } ] }; -describe('VertamediaAdater', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); - - afterEach(() => xhr.restore()); +describe('vertamediaBidAdapter', () => { + const adapter = newBidder(spec); + describe('inherited functions', () => { it('exists and is a function', () => { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); + }); - it('requires paramters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; - }); - - it('requires member && invCode', () => { - let backup = REQUEST.bids[0].params; - REQUEST.bids[0].params = {member: 1234}; - adapter.callBids(REQUEST); - expect(requests).to.be.empty; - REQUEST.bids[0].params = backup; + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(REQUEST)).to.equal(true); }); - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(REQUEST); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('GET'); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, REQUEST); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('response handler', () => { - let server; + describe('buildRequests', () => { + let bidRequests = [REQUEST]; - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + const request = spec.buildRequests(bidRequests, {}); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); + it('sends bid request to ENDPOINT via GET', () => { + expect(request[0].method).to.equal('GET'); + }); + it('sends bid request to correct ENDPOINT', () => { + expect(request[0].url).to.equal(ENDPOINT); }); - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); + it('sends correct bid parameters', () => { + const bid = Object.assign({}, request[0].data); + delete bid.domain; - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + const eq = { + callbackId: '84ab500420319d', + aid: 12345, + w: 640, + h: 480 + }; - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 4.5); + expect(bid).to.deep.equal(eq); }); + }); - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - aid: 356465468, - w: 640, - h: 480, - domain: 'localhost' - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + describe('interpretResponse', () => { + let bidderRequest = {bidderCode: 'bidderCode'}; + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); + const eq = [{ + vastUrl: 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', + descriptionUrl: '44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + bidderCode: 'bidderCode', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 3600, + cpm: 0.9 + }]; + + expect(result).to.deep.equal(eq); }); - it('handles JSON.parse errors', () => { - server.respondWith(''); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('handles nobid responses', () => { + const nobidServerResponse = {bids: []}; + const nobidResult = spec.interpretResponse({body: nobidServerResponse}, {bidderRequest}); - expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + expect(nobidResult.length).to.equal(0); }); }); });