From b6b4fbe87382b1db86d76c843a3de49846cf4ce8 Mon Sep 17 00:00:00 2001 From: Oksana Date: Wed, 11 Mar 2020 19:26:42 +0300 Subject: [PATCH] add mediaforce bid adapter (#4933) * add mediaforce bid adapter * make use of unused variable language --- modules/mediaforceBidAdapter.js | 151 +++++++++++ modules/mediaforceBidAdapter.md | 35 +++ .../spec/modules/mediaforceBidAdapter_spec.js | 240 ++++++++++++++++++ 3 files changed, 426 insertions(+) create mode 100644 modules/mediaforceBidAdapter.js create mode 100644 modules/mediaforceBidAdapter.md create mode 100644 test/spec/modules/mediaforceBidAdapter_spec.js diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js new file mode 100644 index 00000000000..fc0c44f6d82 --- /dev/null +++ b/modules/mediaforceBidAdapter.js @@ -0,0 +1,151 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mediaforce'; +const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!((typeof bid.params === 'object') && bid.params.placement_id && bid.params.publisher_id); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return; + } + + const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : ''; + const dnt = utils.getDNT() ? 1 : 0; + let imp = []; + let requests = [] + validBidRequests.forEach(bid => { + let tagid = bid.params.placement_id; + let bidfloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : 0; + let impObj = { + id: bid.bidId, + tagid: tagid, + secure: 1, + bidfloor: bidfloor, + }; + for (let mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + impObj.banner = createBannerRequest(bid); + imp.push(impObj); + break; + default: return; + } + } + + let request = { + id: bid.transactionId, + site: { + page: referer, + ref: referer, + id: bid.params.publisher_id, + publisher: { + id: bid.params.publisher_id + }, + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: dnt, + language: getLanguage() + }, + imp + }; + requests.push({ + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(request) + }); + }); + return requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const responseBody = serverResponse.body; + const bidResponses = []; + const cur = responseBody.cur; + responseBody.seatbid.forEach((bids) => { + bids.bid.forEach((serverBid) => { + const bid = { + requestId: serverBid.impid, + cpm: parseFloat(serverBid.price), + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.adid, + currency: cur, + netRevenue: true, + ttl: serverBid.ttl || 300, + ad: serverBid.adm, + burl: serverBid.burl, + }; + + bidResponses.push(bid); + }) + }); + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + const cpm = utils.deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (utils.isStr(bid.burl) && bid.burl !== '') { + bid.burl = utils.replaceAuctionPrice(bid.burl, cpm); + utils.triggerPixel(bid.burl); + } + }, +} +registerBidder(spec); + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +function createBannerRequest(bid) { + const sizes = bid.mediaTypes.banner.sizes; + if (!sizes.length) return; + + let format = []; + let r = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + for (let f = 1; f < sizes.length; f++) { + format.push(utils.parseGPTSingleSizeArrayToRtbSize(sizes[f])); + } + if (format.length) { + r.format = format + } + return r +} diff --git a/modules/mediaforceBidAdapter.md b/modules/mediaforceBidAdapter.md new file mode 100644 index 00000000000..e16d4178b3f --- /dev/null +++ b/modules/mediaforceBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: MediaForce Bidder Adapter +Module Type: Bidder Adapter +Maintainer: little.grey.goblin@gmail.com +``` + +# Description + +Module that connects to mediaforce's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: "mediaforce", + params: { + placement_id: 'pl12345', // required + publisher_id: 'pub12345', // required + bidfloor: 0.5, + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js new file mode 100644 index 00000000000..09d997e9349 --- /dev/null +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -0,0 +1,240 @@ +import {assert} from 'chai'; +import {spec} from 'modules/mediaforceBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('mediaforce bid adapter', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + function getLanguage() { + let language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; + } + + const language = getLanguage(); + const baseUrl = 'https://rtb.mfadsrvr.com' + + describe('isBidRequestValid()', function () { + const defaultBid = { + bidder: 'mediaforce', + params: { + property: '10433394', + bidfloor: 0.3, + }, + }; + + it('should not accept bid without required params', function () { + assert.equal(spec.isBidRequestValid(defaultBid), false); + }); + + it('should return false when params are not passed', function () { + let bid = utils.deepClone(defaultBid); + delete bid.params; + assert.equal(spec.isBidRequestValid(bid), false); + }); + + it('should return false when valid params are not passed', function () { + let bid = utils.deepClone(defaultBid); + bid.params = {placement_id: '', publisher_id: ''}; + assert.equal(spec.isBidRequestValid(bid), false); + }); + + it('should return true when valid params are passed', function () { + let bid = utils.deepClone(defaultBid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {publisher_id: 2, placement_id: '123'}; + assert.equal(spec.isBidRequestValid(bid), true); + }); + + it('should return false when mediaTypes == native passed (native is not supported yet)', function () { + let bid = utils.deepClone(defaultBid); + bid.mediaTypes = { + native: { + sizes: [[300, 250]] + } + }; + bid.params = {publisher_id: 2, placement_id: '123'}; + assert.equal(spec.isBidRequestValid(bid), true); + }); + }); + + describe('buildRequests()', function () { + const defaultBid = { + bidder: 'mediaforce', + params: { + publisher_id: 'pub123', + placement_id: '202', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + }; + + const refererInfo = { + referer: 'https://www.prebid.org', + reachedTop: true, + stack: [ + 'https://www.prebid.org/page.html', + 'https://www.prebid.org/iframe1.html', + ] + }; + + const requestUrl = `${baseUrl}/header_bid`; + const dnt = utils.getDNT() ? 1 : 0; + const secure = 1 + + it('should return undefined if no validBidRequests passed', function () { + assert.equal(spec.buildRequests([]), undefined); + }); + + it('should return proper request url: no refererInfo', function () { + let [request] = spec.buildRequests([defaultBid]); + assert.equal(request.url, requestUrl); + }); + + it('should return proper banner imp', function () { + let bid = utils.deepClone(defaultBid); + bid.params.bidfloor = 0.5; + + let bidRequests = [bid]; + let bidderRequest = {bids: bidRequests, refererInfo: refererInfo}; + + let [request] = spec.buildRequests(bidRequests, bidderRequest); + + let data = JSON.parse(request.data); + assert.deepEqual(data, { + id: bid.transactionId, + site: { + id: bid.params.publisher_id, + publisher: {id: bid.params.publisher_id}, + ref: encodeURIComponent(refererInfo.referer), + page: encodeURIComponent(refererInfo.referer), + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: bid.params.placement_id, + secure: secure, + bidfloor: bid.params.bidfloor, + banner: {w: 300, h: 250}, + }], + }); + + assert.deepEqual(request, { + method: 'POST', + url: requestUrl, + data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250}}]}', + }); + }); + + it('multiple sizes', function () { + let bid = utils.deepClone(defaultBid); + bid.mediaTypes = { + banner: { + sizes: [[300, 600], [300, 250]], + } + }; + + let [request] = spec.buildRequests([bid]); + let data = JSON.parse(request.data); + assert.deepEqual(data.imp[0].banner, {w: 300, h: 600, format: [{w: 300, h: 250}]}); + }); + }); + + describe('interpretResponse() banner', function () { + it('not successfull response', function () { + assert.deepEqual(spec.interpretResponse(), []); + }); + + it('successfull response', function () { + let bid = { + price: 3, + w: 100, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + h: 100, + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + adm: `` + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + ad: bid.adm, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + height: bid.h, + netRevenue: true, + burl: bid.burl, + requestId: bid.impid, + ttl: 300, + width: bid.w, + }])); + }); + }); + + describe('onBidWon()', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('should expand price macros in burl', function () { + let burl = 'burl&s=${AUCTION_PRICE}'; + let bid = { + bidder: 'mediaforce', + width: 300, + height: 250, + adId: '330a22bdea4cac', + mediaType: 'banner', + cpm: 0.28, + ad: '...', + requestId: '418b37f85e772c', + adUnitCode: 'div-gpt-ad-1460505748561-0', + size: '350x250', + burl: burl, + adserverTargeting: { + hb_bidder: 'mediaforce', + hb_adid: '330a22bdea4cac', + hb_pb: '0.20', + hb_size: '350x250' + } + } + spec.onBidWon(bid); + assert.equal(bid.burl, 'burl&s=0.20'); + }); + }); +});