diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js new file mode 100644 index 00000000000..1fe139d6fbe --- /dev/null +++ b/modules/undertoneBidAdapter.js @@ -0,0 +1,73 @@ +/** + * Adapter to send bids to Undertone + */ + +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'undertone'; +const URL = '//hb.undertone.com/hb'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (bid && bid.params && bid.params.publisherId && bid.params.placementId) { + bid.params.publisherId = parseInt(bid.params.publisherId); + return true; + } + }, + buildRequests: function(validBidRequests) { + const payload = { + 'x-ut-hb-params': [] + }; + const host = utils.getTopWindowLocation().host; + const domain = /[-\w]+\.(?:[-\w]+\.xn--[-\w]+|[-\w]{3,}|[-\w]+\.[-\w]{2})$/i.exec(host); + + const pubid = validBidRequests[0].params.publisherId; + const REQ_URL = `${URL}?pid=${pubid}&domain=${domain}`; + + validBidRequests.map(bidReq => { + const bid = { + bidRequestId: bidReq.bidId, + hbadaptor: 'prebid', + domain: domain, + placementId: bidReq.params.placementId, + publisherId: bidReq.params.publisherId, + sizes: bidReq.sizes, + params: bidReq.params + }; + payload['x-ut-hb-params'].push(bid); + }); + return { + method: 'POST', + url: REQ_URL, + withCredentials: true, + data: JSON.stringify(payload) + }; + }, + interpretResponse: function(serverResponse, request) { + const bids = []; + const body = serverResponse.body; + + if (body && Array.isArray(body) && body.length > 0) { + body.forEach((bidRes) => { + if (bidRes.ad && bidRes.cpm > 0) { + const bid = { + requestId: bidRes.bidRequestId, + cpm: bidRes.cpm, + width: bidRes.width, + height: bidRes.height, + creativeId: bidRes.adId, + currency: bidRes.currency, + netRevenue: bidRes.netRevenue, + ttl: bidRes.ttl, + ad: bidRes.ad + }; + bids.push(bid); + } + }); + } + return bids; + } +}; +registerBidder(spec); diff --git a/modules/undertoneBidAdapter.md b/modules/undertoneBidAdapter.md new file mode 100644 index 00000000000..8ac84b77bd8 --- /dev/null +++ b/modules/undertoneBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Example Bidder Adapter +Module Type: Bidder Adapter +Maintainer: RampProgrammatic@perion.com +``` +# Description + +Module that connects to Undertone's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "undertone", + params: { + placementId: '10433394', + publisherId: 12345 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js new file mode 100644 index 00000000000..dab9cea98bf --- /dev/null +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -0,0 +1,140 @@ +import { expect } from 'chai'; +import { spec } from 'modules/undertoneBidAdapter'; + +const URL = '//hb.undertone.com/hb'; +const BIDDER_CODE = 'undertone'; +const validBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345 + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + requestId: '9ad1fa8d-2297-4660-a018-b39945054746', + auctionId: '1d1a030790a475' +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '123456789' + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + requestId: '9ad1fa8d-2297-4660-a018-b39945054746' +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345 + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + requestId: '9ad1fa8d-2297-4660-a018-b39945054746', + auctionId: '1d1a030790a475' +}]; + +const validBidRes = { + ad: '
Hello
', + publisherId: 12345, + bidRequestId: '263be71e91dd9d', + adId: 15, + cpm: 100, + nCampaignId: 2, + creativeId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 +}; + +const bidResponse = [validBidRes]; + +const bidResArray = [ + validBidRes, + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + }, + { + ad: '
Hello
', + bidRequestId: '', + cpm: 0, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; + +describe('Undertone Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(undefined); + }); + }); + describe('build request', () => { + it('should send request to correct url via POST', () => { + const request = spec.buildRequests(bidReq); + const domain = null; + const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}`; + expect(request.url).to.equal(REQ_URL); + expect(request.method).to.equal('POST'); + }); + it('should have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const bid = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bid.bidRequestId).to.equal('263be71e91dd9d'); + expect(bid.sizes.length > 0).to.equal(true); + expect(bid.placementId).to.equal('10433394'); + expect(bid.publisherId).to.equal(12345); + expect(bid.params).to.be.an('object'); + }); + }); + + describe('interpretResponse', () => { + it('should build bid array', () => { + let result = spec.interpretResponse({body: bidResponse}); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const result = spec.interpretResponse({body: bidResponse}); + const bid = result[0]; + + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(100); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal(15); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + }); + + it('should return empty array when response is incorrect', () => { + expect(spec.interpretResponse({body: {}}).length).to.equal(0); + expect(spec.interpretResponse({body: []}).length).to.equal(0); + }); + + it('should only use valid bid responses', () => { + expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); + }); + }); +});