diff --git a/adapters.json b/adapters.json index 5edf8f107d0..bf71ad2de53 100644 --- a/adapters.json +++ b/adapters.json @@ -6,6 +6,7 @@ "adform", "adkernel", "admedia", + "vertamedia", "aol", "appnexus", "appnexusAst", @@ -66,6 +67,10 @@ "appnexusAst": { "supportedMediaTypes": ["video"] } + }, { + "vertamedia": { + "supportedMediaTypes": ["video"] + } }, { "rubicon": { diff --git a/src/adapters/vertamedia.js b/src/adapters/vertamedia.js new file mode 100644 index 00000000000..ea9e5402b09 --- /dev/null +++ b/src/adapters/vertamedia.js @@ -0,0 +1,101 @@ +import Adapter from 'src/adapters/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'; + +const ENDPOINT = '//rtb.vertamedia.com/hb/'; + +function VertamediaAdapter() { + var baseAdapter = Adapter.createNew('vertamedia'), + 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' + }); + }; + + function prepareAndSaveRTBRequestParams(bid) { + if (!bid || !bid.params || !bid.params.aid || !bid.placementCode) { + return; + } + + bidRequest = bid; + bidRequest.width = parseInt(bid.sizes[0], 10) || undefined; + bidRequest.height = parseInt(bid.sizes[1], 10) || undefined; + + return { + aid: bid.params.aid, + w: parseInt(bid.sizes[0], 10) || undefined, + h: parseInt(bid.sizes[1], 10) || undefined, + domain: document.location.hostname + }; + } + + /* 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)); + + return; + } + + bidmanager.addBidResponse(bidRequest.placementCode, createBid(STATUS.GOOD, parsed.bids[0])); + } + + function createBid(status, tag) { + var bid = bidfactory.createBid(status, tag); + + 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 { + createNew: VertamediaAdapter.createNew, + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode + }; + +} + +VertamediaAdapter.createNew = function () { + return new VertamediaAdapter(); +}; + +module.exports = VertamediaAdapter; diff --git a/test/spec/adapters/vertamedia_spec.js b/test/spec/adapters/vertamedia_spec.js new file mode 100644 index 00000000000..2b6e9e466e4 --- /dev/null +++ b/test/spec/adapters/vertamedia_spec.js @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import Adapter from 'src/adapters/vertamedia'; +import bidmanager from 'src/bidmanager'; + +const ENDPOINT = 'http://rtb.vertamedia.com/hb/?aid=22489&w=640&h=480&domain=localhost'; + +const REQUEST = { + "bidderCode": "vertamedia", + "requestId": "d3e07445-ab06-44c8-a9dd-5ef9af06d2a6", + "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 +}; +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" + } + ] +}; + + +describe('VertamediaAdater', () => { + + let adapter; + + beforeEach(() => adapter = Adapter.createNew()); + + describe('request function', () => { + + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + 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; + }); + + + 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'); + }); + + }); + + describe('response handler', () => { + + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('registers bids', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + const response = bidmanager.addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('cpm', 4.5); + }); + + 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' + ); + }); + + it('handles JSON.parse errors', () => { + server.respondWith(''); + + adapter.callBids(REQUEST); + server.respond(); + sinon.assert.calledOnce(bidmanager.addBidResponse); + + expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property( + 'statusMessage', + 'Bid returned empty or error response' + ); + }); + }); +});