From aa32519d5ddf54c270e05299eb088723e26a2a52 Mon Sep 17 00:00:00 2001 From: jlzhangdev Date: Wed, 11 Apr 2018 06:25:29 -0700 Subject: [PATCH] EngageBDR legacy bid adapter (#2329) * ebdrBidAdatper * Delete engage_test.html * fix Superfluous trailing arguments issue * add Legacy ebdr adapter test case * add Legacy ebdr adapter test case --- modules/ebdrBidAdapter.js | 183 +++++++++++++++++++++++ modules/ebdrBidAdapter.md | 43 ++++++ test/spec/modules/ebdrBidAdapter_spec.js | 135 +++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 modules/ebdrBidAdapter.js create mode 100644 modules/ebdrBidAdapter.md create mode 100644 test/spec/modules/ebdrBidAdapter_spec.js diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js new file mode 100644 index 00000000000..0a32eb3fe31 --- /dev/null +++ b/modules/ebdrBidAdapter.js @@ -0,0 +1,183 @@ +var CONSTANTS = require('src/constants.json'); +var utils = require('src/utils.js'); +var bidfactory = require('src/bidfactory.js'); +var bidmanager = require('src/bidmanager.js'); +var adloader = require('src/adloader'); +var adaptermanager = require('src/adaptermanager'); + +var defaultPlacementForBadBid = null; + +/** + * Adapter for requesting bids from Ebdr + */ +var EbdrAdapter = function EbdrAdapter() { + var rtbServerDomain = 'dsp.bnmla.com'; + var zoneid; + function _callBids(params) { + var ebdrBids = params.bids || []; + _requestBids(_getTags(ebdrBids)); + } + + // filter bids to de-dupe them? + function _getTags(bids) { + var key; + // var map = {}; + var PubZoneIds = []; + + for (key in bids) { + PubZoneIds.push(bids[key]); + zoneid = utils.getBidIdParameter('zoneid', bids[key].params); + } + + return PubZoneIds; + } + + function getWidthAndHeight(bid) { + var adW = null; + var adH = null; + + var sizeArrayLength = bid.sizes.length; + if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { + adW = bid.sizes[0]; + adH = bid.sizes[1]; + } else { + adW = bid.sizes[0][0]; + adH = bid.sizes[0][1]; + } + + return [adW, adH]; + } + + function _requestBids(bidReqs) { + // build bid request object + var domain = window.location.host; + var page = window.location.pathname + location.search + location.hash; + + var ebdrImps = []; + var ebdrParams = {}; + // assign the first adUnit (placement) for bad bids; + defaultPlacementForBadBid = bidReqs[0].placementCode; + var bidderequestId = bidReqs[0].bidId; + // build impression array for ebdr + utils._each(bidReqs, function(bid) { + var bidFloor = utils.getBidIdParameter('bidfloor', bid.params); + + var whArr = getWidthAndHeight(bid); + ebdrParams['latitude'] = utils.getBidIdParameter('latitude', bid.params); + ebdrParams['longitude'] = utils.getBidIdParameter('longitude', bid.params); + ebdrParams['ifa'] = (utils.getBidIdParameter('IDFA', bid.params).length > utils.getBidIdParameter('ADID', bid.params).length) ? utils.getBidIdParameter('IDFA', bid.params) : utils.getBidIdParameter('ADID', bid.params); + var imp = { + id: bid.bidId, + banner: { + w: whArr[0], + h: whArr[1] + }, + // tagid: tagId, + bidfloor: bidFloor + }; + + ebdrImps.push(imp); + if (bid.params.ebdrDomain != undefined) { + rtbServerDomain = bid.params.ebdrDomain; + } + }); + + // build bid request with impressions + var ebdrBidReq = { + id: bidderequestId, + imp: ebdrImps, + site: { + domain: domain, + page: page + }, + device: { + geo: { + lat: ebdrParams.latitude, + log: ebdrParams.longitude + }, + ifa: ebdrParams.ifa + } + }; + + var scriptUrl = window.location.protocol + '//' + rtbServerDomain + '/hb?callback=window.$$PREBID_GLOBAL$$.ebdrResponse' + + '&zoneid=' + zoneid + '&br=' + encodeURIComponent(JSON.stringify(ebdrBidReq)); + + adloader.loadScript(scriptUrl); + } + + function noBidResponse(bidReqs) { + // no response data + if (!bidReqs.zoneId) { + // no id with which to create an dummy bid + return; + } + + var bid = bidfactory.createBid(2); + bid.bidderCode = 'ebdr'; + bidmanager.addBidResponse(defaultPlacementForBadBid, bid); + } + + // expose the callback to the global object: + $$PREBID_GLOBAL$$.ebdrResponse = function(ebdrResponseObj) { + var bid = {}; + var key; + // valid object? + if (!ebdrResponseObj || !ebdrResponseObj.id) { + return noBidResponse(ebdrResponseObj); + } + + if (!ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { + return noBidResponse(ebdrResponseObj); + } + + for (key in ebdrResponseObj.seatbid[0].bid) { + var ebdrBid = ebdrResponseObj.seatbid[0].bid[key]; + + var responseCPM; + var placementCode = ''; + var id = ebdrBid.impid; + // try to fetch the bid request we sent Ebdr + var bidObj = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'ebdr').bids.find(bid => bid.bidId === id); + if (!bidObj) { + return noBidResponse(ebdrBid); + } + + placementCode = bidObj.placementCode; + bidObj.status = CONSTANTS.STATUS.GOOD; + + // place ad response on bidmanager._adResponsesByBidderId + responseCPM = parseFloat(ebdrBid.price); + + if (responseCPM === 0) { + noBidResponse(ebdrBid); + } + + ebdrBid.placementCode = placementCode; + ebdrBid.size = bidObj.sizes; + var responseAd = ebdrBid.adm; + + // store bid response + // bid status is good (indicating 1) + bid = bidfactory.createBid(1); + bid.creative_id = ebdrBid.Id; + bid.bidderCode = 'ebdr'; + bid.cpm = responseCPM; + + // The bid is a mock bid, the true bidding process happens after the publisher tag is called + bid.ad = decodeURIComponent(responseAd); + + var whArr = getWidthAndHeight(bidObj); + bid.width = whArr[0]; + bid.height = whArr[1]; + + bidmanager.addBidResponse(placementCode, bid); + } + }; // ebdrResponse + return { + callBids: _callBids + }; +}; + +adaptermanager.registerBidAdapter(new EbdrAdapter(), 'ebdr'); + +module.exports = EbdrAdapter; diff --git a/modules/ebdrBidAdapter.md b/modules/ebdrBidAdapter.md new file mode 100644 index 00000000000..523eca73def --- /dev/null +++ b/modules/ebdrBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +**Module Name**: Ebdr Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@engagebdr.com + +# Description + +Module that connects to Ebdr demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'ebdr', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA:'xxx-xxx', + ADID:'xxx-xxx', + latitude:'34.089811', + longitude:'-118.392805' + } + }] + },{ + code: 'div-gpt-ad-1460505748561-1', + sizes: [[728, 90], [970, 90]], + bids: [{ + bidder: 'ebdr', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA:'xxx-xxx', + ADID:'xxx-xxx', + latitude:'34.089811', + longitude:'-118.392805' + } + }] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js new file mode 100644 index 00000000000..fbc868a3882 --- /dev/null +++ b/test/spec/modules/ebdrBidAdapter_spec.js @@ -0,0 +1,135 @@ +var chai = require('chai'); +var Adapter = require('modules/ebdrBidAdapter')(); +var adLoader = require('src/adloader'); +var bidmanager = require('src/bidmanager.js'); +var CONSTANTS = require('src/constants.json'); + +describe('ebdrBidAdapter', function () { + var sandbox; + var bidsRequestedOriginal; + const validBid_1 = { + bidderCode: 'ebdr', + bids: [ + { + bidder: 'ebdr', + bidId: 'bid_id', + // bidderRequestId: 'ebdr1', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA: 'xxx-xxx', + ADID: 'xxx-xxx', + latitude: '34.089811', + longitude: '-118.392805' + }, + placementCode: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [800, 600]] + } + ] + }; + var invalidBid = { + bidderCode: 'ebdr', + bids: [ + { + bidder: 'ebdr', + bidId: 'bid_id', + params: {}, + placementCode: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250]] + } + ] + }; + + var responseWithAd = {'id': 'bid_id', 'seatbid': [{'bid': [{'id': 'bid_id', 'impid': 'bid_id', 'price': 9.81, 'adid': 'abcde-12345', 'nurl': 'http://rex.bnmla.com/pixel?not=1&sid=LRX8nxQ4gX&z=16&rx=1.3392&d=3&dt=3&s=16&px=1.007999', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': 'http://rex.bnmla.com/pixel?not=1&sid=LRX8nxQ4gX&z=16&rx=1.3392&d=3&dt=3&s=16&px=1.007999', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '3d1ec0495ea735'}], 'bidid': '3d1ec0495ea735', 'cur': 'USD'}; + var responseWithoutAd = JSON.stringify({ + 'cpm': 0, + 'url': 'http://p.ato.mx/placement?id=1234', + 'width': 300, + 'height': 250, + 'code': 'ad-unit-1' + }); + + var responseEmpty = ''; + var validJsonParams = { + id: '1234', + prebid: 'ad-unit-1', + size: '300x250' + }; + + beforeEach(() => { + bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; + $$PREBID_GLOBAL$$._bidsRequested = []; + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; + }); + + describe('bid request', function() { + let loadScript; + beforeEach(function () { + loadScript = sinon.stub(adLoader, 'loadScript'); + }); + afterEach(function () { + loadScript.restore(); + }); + it('bid request with valid data', function () { + Adapter.callBids(validBid_1); + sinon.assert.calledOnce(loadScript); + let url = loadScript.firstCall.args[0]; + let callback = loadScript.firstCall.args[1]; + expect(url).to.equal('http://dsp.bnmla.com/hb?callback=window.pbjs.ebdrResponse&zoneid=99999&br=%7B%22id%22%3A%22bid_id%22%2C%22imp%22%3A%5B%7B%22id%22%3A%22bid_id%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22bidfloor%22%3A%221.00%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22localhost%3A9876%22%2C%22page%22%3A%22%2Fcontext.html%22%7D%2C%22device%22%3A%7B%22geo%22%3A%7B%22lat%22%3A%2234.089811%22%2C%22log%22%3A%22-118.392805%22%7D%2C%22ifa%22%3A%22xxx-xxx%22%7D%7D'); + }); + it('bid request with invalid data', function () { + Adapter.callBids(invalidBid); + sinon.assert.calledOnce(loadScript); + let url = loadScript.firstCall.args[0]; + let callback = loadScript.firstCall.args[1]; + expect(url).to.equal('http://dsp.bnmla.com/hb?callback=window.pbjs.ebdrResponse&zoneid=&br=%7B%22id%22%3A%22bid_id%22%2C%22imp%22%3A%5B%7B%22id%22%3A%22bid_id%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22bidfloor%22%3A%22%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22localhost%3A9876%22%2C%22page%22%3A%22%2Fcontext.html%22%7D%2C%22device%22%3A%7B%22geo%22%3A%7B%22lat%22%3A%22%22%2C%22log%22%3A%22%22%7D%2C%22ifa%22%3A%22%22%7D%7D'); + }); + }); + describe('ebdrResponse', () => { + it('should exist and be a function', () => { + expect($$PREBID_GLOBAL$$.ebdrResponse).to.be.a('function'); + }); + }); + + describe('add bids to the manager', () => { + let firstBid; + + beforeEach(() => { + sandbox.stub(bidmanager, 'addBidResponse'); + $$PREBID_GLOBAL$$._bidsRequested.push(validBid_1); + $$PREBID_GLOBAL$$.ebdrResponse(responseWithAd); + firstBid = bidmanager.addBidResponse.firstCall.args[1]; + }); + it('should add a bid object for each bid', () => { + sinon.assert.calledOnce(bidmanager.addBidResponse); + }); + + it('should pass the correct placement code as first param', () => { + let firstPlacementCode = bidmanager.addBidResponse.firstCall.args[0]; + + expect(firstPlacementCode).to.eql('div-gpt-ad-1460505748561-0'); + }); + + it('should have a good statusCode', () => { + expect(firstBid.getStatusCode()).to.eql(1); + }); + + it('should add the CPM to the bid object', () => { + expect(firstBid).to.have.property('cpm', 9.81); + }); + + it('should include the ad to the bid object', () => { + expect(firstBid).to.have.property('ad'); + }); + + it('should include the size to the bid object', () => { + expect(firstBid).to.have.property('width', 300); + expect(firstBid).to.have.property('height', 250); + }); + }); +});