From 2fba6a29ce0f473e4b174c35afdd9b535d363699 Mon Sep 17 00:00:00 2001 From: anand-venkatraman Date: Fri, 14 Oct 2016 08:31:49 -0400 Subject: [PATCH 01/12] ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change --- src/adapters/analytics/pulsepoint.js | 12 +++ test/spec/adapters/pulsepoint_spec.js | 143 ++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/adapters/analytics/pulsepoint.js create mode 100644 test/spec/adapters/pulsepoint_spec.js diff --git a/src/adapters/analytics/pulsepoint.js b/src/adapters/analytics/pulsepoint.js new file mode 100644 index 00000000000..28f4e7f1c78 --- /dev/null +++ b/src/adapters/analytics/pulsepoint.js @@ -0,0 +1,12 @@ +/** + * pulsepoint.js - Analytics Adapter for PulsePoint + */ + +import adapter from 'AnalyticsAdapter'; + +export default adapter({ + global: 'PulsePointPrebidAnalytics', + handler: 'on', + analyticsType: 'bundle' + }); + diff --git a/test/spec/adapters/pulsepoint_spec.js b/test/spec/adapters/pulsepoint_spec.js new file mode 100644 index 00000000000..fe5dee461db --- /dev/null +++ b/test/spec/adapters/pulsepoint_spec.js @@ -0,0 +1,143 @@ +import {expect} from 'chai'; +import PulsePointAdapter from '../../../src/adapters/pulsepoint'; +import bidManager from '../../../src/bidmanager'; +import adLoader from '../../../src/adloader'; + +describe("PulsePoint Adapter Tests", () => { + + let pulsepointAdapter = new PulsePointAdapter(); + let slotConfigs; + let requests = []; + let responses = {}; + + function initPulsepointLib() { + /* Mocked PulsePoint library */ + window.pp = { + requestActions: { + BID: 0 + } + }; + /* Ad object*/ + window.pp.Ad = function(config) { + this.display = function() { + requests.push(config); + config.callback(responses[config.ct]); + }; + }; + } + + function resetPulsepointLib() { + window.pp = undefined; + } + + beforeEach(() => { + initPulsepointLib(); + sinon.stub(bidManager, 'addBidResponse'); + sinon.stub(adLoader, 'loadScript'); + + slotConfigs = { + bids: [ + { + placementCode: "/DfpAccount1/slot1", + bidder: "pulsepoint", + params: { + cp: "p10000", + ct: "t10000", + cf: "300x250" + } + },{ + placementCode: "/DfpAccount2/slot2", + bidder: "pulsepoint", + params: { + cp: "p20000", + ct: "t20000", + cf: "728x90" + } + } + ] + }; + }); + + afterEach(() => { + bidManager.addBidResponse.restore(); + adLoader.loadScript.restore(); + requests = []; + responses = {}; + }); + + it('Verify requests sent to PulsePoint library', () => { + pulsepointAdapter.callBids(slotConfigs); + expect(requests).to.have.length(2); + //slot 1 + expect(requests[0].cp).to.equal('p10000'); + expect(requests[0].ct).to.equal('t10000'); + expect(requests[0].cf).to.equal('300x250'); + expect(requests[0].ca).to.equal(0); + expect(requests[0].cn).to.equal(1); + expect(requests[0].cu).to.equal('http://bid.contextweb.com/header/tag'); + expect(requests[0].adUnitId).to.equal('/DfpAccount1/slot1'); + expect(requests[0]).to.have.property('callback'); + // //slot 2 + expect(requests[1].cp).to.equal('p20000'); + expect(requests[1].ct).to.equal('t20000'); + expect(requests[1].cf).to.equal('728x90'); + expect(requests[1].ca).to.equal(0); + expect(requests[1].cn).to.equal(1); + expect(requests[1].cu).to.equal('http://bid.contextweb.com/header/tag'); + expect(requests[1].adUnitId).to.equal('/DfpAccount2/slot2'); + expect(requests[1]).to.have.property('callback'); + }); + + it('Verify bid', () => { + responses['t10000'] = { + html: 'This is an Ad', + bidCpm: 1.25 + }; + pulsepointAdapter.callBids(slotConfigs); + let placement = bidManager.addBidResponse.firstCall.args[0]; + let bid = bidManager.addBidResponse.firstCall.args[1]; + expect(placement).to.equal('/DfpAccount1/slot1'); + expect(bid.bidderCode).to.equal('pulsepoint'); + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad'); + expect(bid.width).to.equal('300'); + expect(bid.height).to.equal('250'); + }); + + it('Verify passback', () => { + pulsepointAdapter.callBids(slotConfigs); + let placement = bidManager.addBidResponse.firstCall.args[0]; + let bid = bidManager.addBidResponse.firstCall.args[1]; + expect(placement).to.equal('/DfpAccount1/slot1'); + expect(bid.bidderCode).to.equal('pulsepoint'); + expect(bid).to.not.have.property('ad'); + expect(bid).to.not.have.property('cpm'); + }); + + it('Verify PulsePoint library is downloaded if nessesary', () => { + resetPulsepointLib(); + pulsepointAdapter.callBids(slotConfigs); + let libraryLoadCall = adLoader.loadScript.firstCall.args[0]; + let callback = adLoader.loadScript.firstCall.args[1]; + expect(libraryLoadCall).to.equal('http://tag.contextweb.com/getjs.static.js'); + expect(callback).to.be.a('function'); + }); + + it('Verify Bids get processed after PulsePoint library downloads', () => { + resetPulsepointLib(); + pulsepointAdapter.callBids(slotConfigs); + let callback = adLoader.loadScript.firstCall.args[1]; + let bidCall = bidManager.addBidResponse.firstCall; + expect(callback).to.be.a('function'); + expect(bidCall).to.be.a('null'); + //the library load should initialize pulsepoint lib + initPulsepointLib(); + callback(); + expect(requests.length).to.equal(2); + bidCall = bidManager.addBidResponse.firstCall; + expect(bidCall).to.be.a('object'); + expect(bidCall.args[0]).to.equal('/DfpAccount1/slot1'); + expect(bidCall.args[1]).to.be.a('object'); + }); + +}); From 5da43c32c0d739bb1946601bdf7bf713cd7b1d7c Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Tue, 25 Oct 2016 15:56:41 -0400 Subject: [PATCH 02/12] Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 --- src/adapters/pulsepoint.js | 4 ++-- test/spec/adapters/pulsepoint_spec.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/adapters/pulsepoint.js b/src/adapters/pulsepoint.js index fa2d2f73748..ef82b05f4e5 100644 --- a/src/adapters/pulsepoint.js +++ b/src/adapters/pulsepoint.js @@ -43,7 +43,7 @@ var PulsePointAdapter = function PulsePointAdapter() { function bidResponseAvailable(bidRequest, bidResponse) { if (bidResponse) { var adSize = bidRequest.params.cf.toUpperCase().split('X'); - var bid = bidfactory.createBid(1); + var bid = bidfactory.createBid(1, bidRequest); bid.bidderCode = bidRequest.bidder; bid.cpm = bidResponse.bidCpm; bid.ad = bidResponse.html; @@ -51,7 +51,7 @@ var PulsePointAdapter = function PulsePointAdapter() { bid.height = adSize[1]; bidmanager.addBidResponse(bidRequest.placementCode, bid); } else { - var passback = bidfactory.createBid(2); + var passback = bidfactory.createBid(2, bidRequest); passback.bidderCode = bidRequest.bidder; bidmanager.addBidResponse(bidRequest.placementCode, passback); } diff --git a/test/spec/adapters/pulsepoint_spec.js b/test/spec/adapters/pulsepoint_spec.js index fe5dee461db..96755a36207 100644 --- a/test/spec/adapters/pulsepoint_spec.js +++ b/test/spec/adapters/pulsepoint_spec.js @@ -40,6 +40,7 @@ describe("PulsePoint Adapter Tests", () => { { placementCode: "/DfpAccount1/slot1", bidder: "pulsepoint", + bidId: 'bid12345', params: { cp: "p10000", ct: "t10000", @@ -48,6 +49,7 @@ describe("PulsePoint Adapter Tests", () => { },{ placementCode: "/DfpAccount2/slot2", bidder: "pulsepoint", + bidId: 'bid23456', params: { cp: "p20000", ct: "t20000", @@ -102,6 +104,7 @@ describe("PulsePoint Adapter Tests", () => { expect(bid.ad).to.equal('This is an Ad'); expect(bid.width).to.equal('300'); expect(bid.height).to.equal('250'); + expect(bid.adId).to.equal('bid12345'); }); it('Verify passback', () => { @@ -112,6 +115,7 @@ describe("PulsePoint Adapter Tests", () => { expect(bid.bidderCode).to.equal('pulsepoint'); expect(bid).to.not.have.property('ad'); expect(bid).to.not.have.property('cpm'); + expect(bid.adId).to.equal('bid12345'); }); it('Verify PulsePoint library is downloaded if nessesary', () => { From 62756a9cf82a835fd2e77f0efc1449c84d748467 Mon Sep 17 00:00:00 2001 From: anand-venkatraman Date: Wed, 9 Nov 2016 10:57:09 -0500 Subject: [PATCH 03/12] ET-1765: Adding support for additional params in PulsePoint adapter (#2) --- src/adapters/pulsepoint.js | 29 +++++++++++++++++---------- test/spec/adapters/pulsepoint_spec.js | 6 +++++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/adapters/pulsepoint.js b/src/adapters/pulsepoint.js index ef82b05f4e5..42479b895a3 100644 --- a/src/adapters/pulsepoint.js +++ b/src/adapters/pulsepoint.js @@ -19,21 +19,28 @@ var PulsePointAdapter = function PulsePointAdapter() { var bids = params.bids; for (var i = 0; i < bids.length; i++) { var bidRequest = bids[i]; - var callback = bidResponseCallback(bidRequest); - var ppBidRequest = new window.pp.Ad({ - cf: bidRequest.params.cf, - cp: bidRequest.params.cp, - ct: bidRequest.params.ct, - cn: 1, - ca: window.pp.requestActions.BID, - cu: bidUrl, - adUnitId: bidRequest.placementCode, - callback: callback - }); + var ppBidRequest = new window.pp.Ad(bidRequestOptions(bidRequest)); ppBidRequest.display(); } } + function bidRequestOptions(bidRequest) { + var callback = bidResponseCallback(bidRequest); + var options = { + cn: 1, + ca: window.pp.requestActions.BID, + cu: bidUrl, + adUnitId: bidRequest.placementCode, + callback: callback + }; + for(var param in bidRequest.params) { + if(bidRequest.params.hasOwnProperty(param)) { + options[param] = bidRequest.params[param]; + } + } + return options; + } + function bidResponseCallback(bid) { return function (bidResponse) { bidResponseAvailable(bid, bidResponse); diff --git a/test/spec/adapters/pulsepoint_spec.js b/test/spec/adapters/pulsepoint_spec.js index 96755a36207..9c901cd149b 100644 --- a/test/spec/adapters/pulsepoint_spec.js +++ b/test/spec/adapters/pulsepoint_spec.js @@ -44,7 +44,9 @@ describe("PulsePoint Adapter Tests", () => { params: { cp: "p10000", ct: "t10000", - cf: "300x250" + cf: "300x250", + param1: "value1", + param2: 2 } },{ placementCode: "/DfpAccount2/slot2", @@ -79,6 +81,8 @@ describe("PulsePoint Adapter Tests", () => { expect(requests[0].cu).to.equal('http://bid.contextweb.com/header/tag'); expect(requests[0].adUnitId).to.equal('/DfpAccount1/slot1'); expect(requests[0]).to.have.property('callback'); + expect(requests[0].param1).to.equal('value1'); + expect(requests[0].param2).to.equal(2); // //slot 2 expect(requests[1].cp).to.equal('p20000'); expect(requests[1].ct).to.equal('t20000'); From b9af15cac48fc7180ef79c6a7936ae721c6a0fb1 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Thu, 8 Dec 2016 12:48:40 -0500 Subject: [PATCH 04/12] ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 --- src/adapters/pulsepoint.js | 11 +++++++++++ test/spec/adapters/pulsepoint_spec.js | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/adapters/pulsepoint.js b/src/adapters/pulsepoint.js index 42479b895a3..7e882ea8591 100644 --- a/src/adapters/pulsepoint.js +++ b/src/adapters/pulsepoint.js @@ -1,6 +1,7 @@ var bidfactory = require('../bidfactory.js'); var bidmanager = require('../bidmanager.js'); var adloader = require('../adloader.js'); +var utils = require('../utils.js'); var PulsePointAdapter = function PulsePointAdapter() { @@ -19,8 +20,18 @@ var PulsePointAdapter = function PulsePointAdapter() { var bids = params.bids; for (var i = 0; i < bids.length; i++) { var bidRequest = bids[i]; + requestBid(bidRequest); + } + } + + function requestBid(bidRequest) { + try { var ppBidRequest = new window.pp.Ad(bidRequestOptions(bidRequest)); ppBidRequest.display(); + } catch(e) { + //register passback on any exceptions while attempting to fetch response. + utils.logMessage('pulsepoint.requestBid', 'ERROR', e); + bidResponseAvailable(bidRequest); } } diff --git a/test/spec/adapters/pulsepoint_spec.js b/test/spec/adapters/pulsepoint_spec.js index 9c901cd149b..0e9fb97fa4c 100644 --- a/test/spec/adapters/pulsepoint_spec.js +++ b/test/spec/adapters/pulsepoint_spec.js @@ -148,4 +148,18 @@ describe("PulsePoint Adapter Tests", () => { expect(bidCall.args[1]).to.be.a('object'); }); + //related to issue https://github.com/prebid/Prebid.js/issues/866 + it('Verify Passbacks when window.pp is not available', () => { + window.pp = function() {}; + pulsepointAdapter.callBids(slotConfigs); + let placement = bidManager.addBidResponse.firstCall.args[0]; + let bid = bidManager.addBidResponse.firstCall.args[1]; + //verify that we passed back without exceptions, should window.pp be already taken. + expect(placement).to.equal('/DfpAccount1/slot1'); + expect(bid.bidderCode).to.equal('pulsepoint'); + expect(bid).to.not.have.property('ad'); + expect(bid).to.not.have.property('cpm'); + expect(bid.adId).to.equal('bid12345'); + }); + }); From b5eeb7f618fbe62c81c4c3f80a293f7e12a9aaad Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Thu, 8 Dec 2016 17:34:55 -0500 Subject: [PATCH 05/12] Minor fix --- src/adapters/pulsepoint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/pulsepoint.js b/src/adapters/pulsepoint.js index 7e882ea8591..aaad60ee311 100644 --- a/src/adapters/pulsepoint.js +++ b/src/adapters/pulsepoint.js @@ -30,7 +30,7 @@ var PulsePointAdapter = function PulsePointAdapter() { ppBidRequest.display(); } catch(e) { //register passback on any exceptions while attempting to fetch response. - utils.logMessage('pulsepoint.requestBid', 'ERROR', e); + utils.logError('pulsepoint.requestBid', 'ERROR', e); bidResponseAvailable(bidRequest); } } From d68e9ee3fbd067422995470c990879107fc29bc6 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Fri, 22 Sep 2017 16:13:26 -0400 Subject: [PATCH 06/12] Refactoring current functionality to work with bidderFactory (for 1.0 migration) --- modules/pulsepointLiteBidAdapter.js | 491 +++++++++--------- .../modules/pulsepointLiteBidAdapter_spec.js | 176 +++---- 2 files changed, 300 insertions(+), 367 deletions(-) diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index a9485823efc..3ded202e2eb 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -1,9 +1,15 @@ import {createBid} from 'src/bidfactory'; -import {addBidResponse} from 'src/bidmanager'; import {logError, getTopWindowLocation} from 'src/utils'; -import {ajax} from 'src/ajax'; import {STATUS} from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; /** * PulsePoint "Lite" Adapter. This adapter implementation is lighter than the @@ -11,291 +17,274 @@ import adaptermanager from 'src/adaptermanager'; * dependencies and relies on a single OpenRTB request to the PulsePoint * bidder instead of separate requests per slot. */ -function PulsePointLiteAdapter() { - const bidUrl = window.location.protocol + '//bid.contextweb.com/header/ortb'; - const ajaxOptions = { - method: 'POST', - withCredentials: true, - contentType: 'text/plain' - }; - const NATIVE_DEFAULTS = { - TITLE_LEN: 100, - DESCR_LEN: 200, - SPONSORED_BY_LEN: 50, - IMG_MIN: 150, - ICON_MIN: 50, - }; +export const spec = { - /** - * Makes the call to PulsePoint endpoint and registers bids. - */ - function _callBids(bidRequest) { - try { - // construct the openrtb bid request from slots - const request = { - imp: bidRequest.bids.map(slot => impression(slot)), - site: site(bidRequest), - device: device(), - }; - ajax(bidUrl, (rawResponse) => { - bidResponseAvailable(bidRequest, rawResponse); - }, JSON.stringify(request), ajaxOptions); - } catch (e) { - // register passback on any exceptions while attempting to fetch response. - logError('pulsepoint.requestBid', 'ERROR', e); - bidResponseAvailable(bidRequest); - } - } + code: 'pulseLite', - /** - * Callback for bids, after the call to PulsePoint completes. - */ - function bidResponseAvailable(bidRequest, rawResponse) { - const idToSlotMap = {}; - const idToBidMap = {}; - // extract the request bids and the response bids, keyed by impr-id - bidRequest.bids.forEach((slot) => { - idToSlotMap[slot.bidId] = slot; - }); - const bidResponse = parse(rawResponse); - if (bidResponse) { - bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach((bid) => { - idToBidMap[bid.impid] = bid; - })); - } - // register the responses - Object.keys(idToSlotMap).forEach((id) => { - if (idToBidMap[id]) { - const size = adSize(idToSlotMap[id]); - const bid = createBid(STATUS.GOOD, bidRequest); - bid.bidderCode = bidRequest.bidderCode; - bid.cpm = idToBidMap[id].price; - bid.adId = id; - if (isNative(idToSlotMap[id])) { - bid.native = nativeResponse(idToSlotMap[id], idToBidMap[id]); - bid.mediaType = 'native'; - } else { - bid.ad = idToBidMap[id].adm; - bid.width = size[0]; - bid.height = size[1]; - } - addBidResponse(idToSlotMap[id].placementCode, bid); - } else { - const passback = createBid(STATUS.NO_BID, bidRequest); - passback.bidderCode = bidRequest.bidderCode; - passback.adId = id; - addBidResponse(idToSlotMap[id].placementCode, passback); - } - }); - } + aliases: ['pulsepointLite'], - /** - * Produces an OpenRTBImpression from a slot config. - */ - function impression(slot) { + supportedMediaTypes: ['native'], + + isBidRequestValid: bid => { + return bid.params.cp && bid.params.ct; + }, + + buildRequests: bidRequest => { + const request = { + imp: bidRequest.map(slot => impression(slot)), + site: site(bidRequest), + device: device(), + }; return { - id: slot.bidId, - banner: banner(slot), - native: native(slot), - tagid: slot.params.ct.toString(), + method: 'POST', + url: '//bid.contextweb.com/header/ortb', + data: JSON.stringify(request), }; - } + }, - /** - * Produces an OpenRTB Banner object for the slot given. - */ - function banner(slot) { - const size = adSize(slot); - return slot.nativeParams ? null : { - w: size[0], - h: size[1], - }; - } + interpretResponse: (response, request) => { + return bidResponseAvailable(request, response); + }, - /** - * Produces an OpenRTB Native object for the slot given. - */ - function native(slot) { - if (slot.nativeParams) { - const assets = []; - addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); - addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); - addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); - addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); - addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); - return { - request: JSON.stringify({ assets }), - ver: '1.1', - }; + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//bh.contextweb.com/visitormatch' + }]; } - return null; } - /** - * Helper method to add an asset to the assets list. - */ - function addAsset(assets, asset) { - if (asset) { - assets.push(asset); - } - } +}; - /** - * Produces a Native Title asset for the configuration given. - */ - function titleAsset(id, params, defaultLen) { - if (params) { - return { - id: id, - required: params.required ? 1 : 0, - title: { - len: params.len || defaultLen, - }, +/** + * Callback for bids, after the call to PulsePoint completes. + */ +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + // extract the request bids and the response bids, keyed by impr-id + const ortbRequest = parse(bidRequest.data); + ortbRequest.imp.forEach((imp) => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach((bid) => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach((id) => { + if (idToBidMap[id]) { + const bid = { + requestId: id, + cpm: idToBidMap[id].price, + creative_id: id, + adId: id, }; + if (idToImpMap[id].native) { + bid.native = nativeResponse(idToImpMap[id], idToBidMap[id]); + bid.mediaType = 'native'; + } else { + bid.ad = idToBidMap[id].adm; + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + } + bids.push(bid); } - return null; - } + }); + return bids; +} - /** - * Produces a Native Image asset for the configuration given. - */ - function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { - return params ? { - id: id, - required: params.required ? 1 : 0, - img: { - type, - wmin: params.wmin || defaultMinWidth, - hmin: params.hmin || defaultMinHeight, - } - } : null; - } +/** + * Produces an OpenRTBImpression from a slot config. + */ +function impression(slot) { + return { + id: slot.bidId, + banner: banner(slot), + native: native(slot), + tagid: slot.params.ct.toString(), + }; +} - /** - * Produces a Native Data asset for the configuration given. - */ - function dataAsset(id, params, type, defaultLen) { - return params ? { - id: id, - required: params.required ? 1 : 0, - data: { - type, - len: params.len || defaultLen, - } - } : null; - } +/** + * Produces an OpenRTB Banner object for the slot given. + */ +function banner(slot) { + const size = adSize(slot); + return slot.nativeParams ? null : { + w: size[0], + h: size[1], + }; +} - /** - * Produces an OpenRTB site object. - */ - function site(bidderRequest) { - const pubId = bidderRequest.bids.length > 0 ? bidderRequest.bids[0].params.cp : '0'; +/** + * Produces an OpenRTB Native object for the slot given. + */ +function native(slot) { + if (slot.nativeParams) { + const assets = []; + addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); return { - publisher: { - id: pubId.toString(), - }, - ref: referrer(), - page: getTopWindowLocation().href, + request: JSON.stringify({ assets }), + ver: '1.1', }; } + return null; +} - /** - * Attempts to capture the referrer url. - */ - function referrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; - } +/** + * Helper method to add an asset to the assets list. + */ +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); } +} - /** - * Produces an OpenRTB Device object. - */ - function device() { +/** + * Produces a Native Title asset for the configuration given. + */ +function titleAsset(id, params, defaultLen) { + if (params) { return { - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + id: id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, + }, }; } + return null; +} - /** - * Safely parses the input given. Returns null on - * parsing failure. - */ - function parse(rawResponse) { - try { - if (rawResponse) { - return JSON.parse(rawResponse); - } - } catch (ex) { - logError('pulsepointLite.safeParse', 'ERROR', ex); +/** + * Produces a Native Image asset for the configuration given. + */ +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id: id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, } - return null; - } + } : null; +} - /** - * Determines the AdSize for the slot. - */ - function adSize(slot) { - if (slot.params.cf) { - const size = slot.params.cf.toUpperCase().split('X'); - const width = parseInt(slot.params.cw || size[0], 10); - const height = parseInt(slot.params.ch || size[1], 10); - return [width, height]; +/** + * Produces a Native Data asset for the configuration given. + */ +function dataAsset(id, params, type, defaultLen) { + return params ? { + id: id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, } - return [1, 1]; + } : null; +} + +/** + * Produces an OpenRTB site object. + */ +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; + return { + publisher: { + id: pubId.toString(), + }, + ref: referrer(), + page: getTopWindowLocation().href, + }; +} + +/** + * Attempts to capture the referrer url. + */ +function referrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; } +} - /** - * Parses the native response from the Bid given. - */ - function nativeResponse(slot, bid) { - if (slot.nativeParams) { - const nativeAd = parse(bid.adm); - const keys = {}; - if (nativeAd && nativeAd.native && nativeAd.native.assets) { - nativeAd.native.assets.forEach((asset) => { - keys.title = asset.title ? asset.title.text : keys.title; - keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body; - keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy; - keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image; - keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon; - }); - if (nativeAd.native.link) { - keys.clickUrl = encodeURIComponent(nativeAd.native.link.url); - } - keys.impressionTrackers = nativeAd.native.imptrackers; - return keys; - } +/** + * Produces an OpenRTB Device object. + */ +function device() { + return { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; +} + +/** + * Safely parses the input given. Returns null on + * parsing failure. + */ +function parse(rawResponse) { + try { + if (rawResponse) { + return JSON.parse(rawResponse); } - return null; + } catch (ex) { + logError('pulsepointLite.safeParse', 'ERROR', ex); } + return null; +} - /** - * Parses the native response from the Bid given. - */ - function isNative(slot) { - return !!slot.nativeParams; +/** + * Determines the AdSize for the slot. + */ +function adSize(slot) { + if (slot.params.cf) { + const size = slot.params.cf.toUpperCase().split('X'); + const width = parseInt(slot.params.cw || size[0], 10); + const height = parseInt(slot.params.ch || size[1], 10); + return [width, height]; } + return [1, 1]; +} - return Object.assign(this, { - callBids: _callBids - }); +/** + * Parses the native response from the Bid given. + */ +function nativeResponse(imp, bid) { + if (imp.native) { + const nativeAd = parse(bid.adm); + const keys = {}; + if (nativeAd && nativeAd.native && nativeAd.native.assets) { + nativeAd.native.assets.forEach((asset) => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy; + keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image; + keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon; + }); + if (nativeAd.native.link) { + keys.clickUrl = encodeURIComponent(nativeAd.native.link.url); + } + keys.impressionTrackers = nativeAd.native.imptrackers; + return keys; + } + } + return null; } /** - * "pulseLite" will be the adapter name going forward. "pulsepointLite" to be - * deprecated, but kept here for backwards compatibility. - * Reason is key truncation. When the Publisher opts for sending all bids to DFP, then - * the keys get truncated due to the limit in key-size (20 characters, detailed - * here https://support.google.com/dfp_premium/answer/1628457?hl=en). Here is an - * example, where keys got truncated when using the "pulsepointLite" alias - "hb_adid_pulsepointLi=1300bd87d59c4c2" -*/ -adaptermanager.registerBidAdapter(new PulsePointLiteAdapter(), 'pulseLite', { - supportedMediaTypes: [ 'native' ] -}); -adaptermanager.aliasBidAdapter('pulseLite', 'pulsepointLite'); + * Parses the native response from the Bid given. + */ +function isNative(slot) { + return !!slot.nativeParams; +} -module.exports = PulsePointLiteAdapter; +registerBidder(spec); diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index f7b7a790302..e8c57055516 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -1,71 +1,51 @@ import {expect} from 'chai'; -import PulsePointAdapter from 'modules/pulsepointLiteBidAdapter'; +import {spec} from 'modules/pulsepointLiteBidAdapter'; import bidManager from 'src/bidmanager'; import {getTopWindowLocation} from 'src/utils'; -import * as ajax from 'src/ajax'; +import {newBidder} from 'src/adapters/bidderFactory'; describe('PulsePoint Lite Adapter Tests', () => { - let pulsepointAdapter = new PulsePointAdapter(); let slotConfigs; let nativeSlotConfig; - let ajaxStub; beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - ajaxStub = sinon.stub(ajax, 'ajax'); - - slotConfigs = { - bidderCode: 'pulseLite', - bids: [ - { - placementCode: '/DfpAccount1/slot1', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '300x250' - } - }, { - placementCode: '/DfpAccount2/slot2', - bidId: 'bid23456', - params: { - cp: 'p10000', - ct: 't20000', - cf: '728x90' - } - } - ] - }; - nativeSlotConfig = { - bidderCode: 'pulseLite', - bids: [ - { - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - nativeParams: { - title: { required: true, len: 200 }, - image: { wmin: 100 }, - sponsoredBy: { } - }, - params: { - cp: 'p10000', - ct: 't10000' - } - } - ] - }; - }); - - afterEach(() => { - bidManager.addBidResponse.restore(); - ajaxStub.restore(); + slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '300x250' + } + }, { + placementCode: '/DfpAccount2/slot2', + bidId: 'bid23456', + params: { + cp: 'p10000', + ct: 't20000', + cf: '728x90' + } + }]; + nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: { } + }, + params: { + cp: 'p10000', + ct: 't10000' + } + }]; }); - it('Verify requests sent to PulsePoint', () => { - pulsepointAdapter.callBids(slotConfigs); - expect(ajaxStub.callCount).to.equal(1); - expect(ajaxStub.firstCall.args[0]).to.equal('http://bid.contextweb.com/header/ortb'); - const ortbRequest = JSON.parse(ajaxStub.firstCall.args[2]); + it('Verify build request', () => { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); // site object expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); @@ -88,11 +68,10 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(ortbRequest.imp[1].banner.h).to.equal(90); }); - it('Verify bid', () => { - pulsepointAdapter.callBids(slotConfigs); - // trigger a mock ajax callback with bid. - const ortbRequest = JSON.parse(ajaxStub.firstCall.args[2]); - ajaxStub.firstCall.args[1](JSON.stringify({ + it('Verify parse response', () => { + const request = spec.buildRequests(slotConfigs); + const ortbRequest = JSON.parse(request.data); + const ortbResponse = { seatbid: [{ bid: [{ impid: ortbRequest.imp[0].id, @@ -100,57 +79,29 @@ describe('PulsePoint Lite Adapter Tests', () => { adm: 'This is an Ad' }] }] - })); - expect(bidManager.addBidResponse.callCount).to.equal(2); + }; + const bids = spec.interpretResponse(ortbResponse, request); + expect(bids).to.have.lengthOf(1); // verify first bid - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulseLite'); + const bid = bids[0]; expect(bid.cpm).to.equal(1.25); expect(bid.ad).to.equal('This is an Ad'); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.adId).to.equal('bid12345'); - // verify passback on 2nd impression. - placement = bidManager.addBidResponse.secondCall.args[0]; - bid = bidManager.addBidResponse.secondCall.args[1]; - expect(placement).to.equal('/DfpAccount2/slot2'); - expect(bid.adId).to.equal('bid23456'); - expect(bid.bidderCode).to.equal('pulseLite'); - expect(bid.cpm).to.be.undefined; }); it('Verify full passback', () => { - pulsepointAdapter.callBids(slotConfigs); - // trigger a mock ajax callback with no bid. - ajaxStub.firstCall.args[1](null); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulseLite'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - expect(bid.adId).to.equal('bid12345'); - }); - - it('Verify passback when ajax call fails', () => { - ajaxStub.throws(); - pulsepointAdapter.callBids(slotConfigs); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulseLite'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - expect(bid.adId).to.equal('bid12345'); + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse(null, request) + expect(bids).to.have.lengthOf(0); }); it('Verify Native request', () => { - pulsepointAdapter.callBids(nativeSlotConfig); - expect(ajaxStub.callCount).to.equal(1); - expect(ajaxStub.firstCall.args[0]).to.equal('http://bid.contextweb.com/header/ortb'); - const ortbRequest = JSON.parse(ajaxStub.firstCall.args[2]); + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); // native impression expect(ortbRequest.imp[0].tagid).to.equal('t10000'); expect(ortbRequest.imp[0].banner).to.equal(null); @@ -184,10 +135,10 @@ describe('PulsePoint Lite Adapter Tests', () => { }); it('Verify Native response', () => { - pulsepointAdapter.callBids(nativeSlotConfig); - expect(ajaxStub.callCount).to.equal(1); - expect(ajaxStub.firstCall.args[0]).to.equal('http://bid.contextweb.com/header/ortb'); - const ortbRequest = JSON.parse(ajaxStub.firstCall.args[2]); + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); const nativeResponse = { native: { assets: [ @@ -199,7 +150,7 @@ describe('PulsePoint Lite Adapter Tests', () => { imptrackers: [ 'http://imp1.trackme.com/', 'http://imp1.contextweb.com/' ] } }; - ajaxStub.firstCall.args[1](JSON.stringify({ + const ortbResponse = { seatbid: [{ bid: [{ impid: ortbRequest.imp[0].id, @@ -207,12 +158,10 @@ describe('PulsePoint Lite Adapter Tests', () => { adm: JSON.stringify(nativeResponse) }] }] - })); + }; + const bids = spec.interpretResponse(ortbResponse, request); // verify bid - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot3'); - expect(bid.bidderCode).to.equal('pulseLite'); + const bid = bids[0]; expect(bid.cpm).to.equal(1.25); expect(bid.adId).to.equal('bid12345'); expect(bid.ad).to.be.undefined; @@ -226,9 +175,4 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(bid.native.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); expect(bid.native.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); }); - - it('Verify adapter interface', function () { - const adapter = new PulsePointAdapter(); - expect(adapter).to.have.property('callBids'); - }); }); From 881ea4f4d92b3447c7d0756ac0b3d3cc8de214ee Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Fri, 22 Sep 2017 16:48:54 -0400 Subject: [PATCH 07/12] More tests. --- modules/pulsepointLiteBidAdapter.js | 2 +- .../modules/pulsepointLiteBidAdapter_spec.js | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index 3ded202e2eb..da34de0fd26 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -26,7 +26,7 @@ export const spec = { supportedMediaTypes: ['native'], isBidRequestValid: bid => { - return bid.params.cp && bid.params.ct; + return !!(bid && bid.params && bid.params.cp && bid.params.ct); }, buildRequests: bidRequest => { diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index e8c57055516..7899b6f7caa 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -175,4 +175,39 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(bid.native.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); expect(bid.native.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); }); + + it('Verifies bidder code', () => { + expect(spec.code).to.equal('pulseLite'); + }); + + it('Verifies bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('pulsepointLite'); + }); + + it('Verifies supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('native'); + }); + + it('Verifies if bid request valid', () => { + expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); + expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { ct: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { cp: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { ct: 123, cp: 234 }})).to.equal(true); + }); + + it('Verifies sync options', () => { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({ iframeEnabled: false})).to.be.undefined; + const options = spec.getUserSyncs({ iframeEnabled: true}); + expect(options).to.not.be.undefined; + expect(options).to.have.lengthOf(1); + expect(options[0].type).to.equal('iframe'); + expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch'); + }); }); From a655e4384327ca989793ff809fdd6b9820859fdb Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Fri, 22 Sep 2017 17:17:55 -0400 Subject: [PATCH 08/12] Adding support for "app" requests. --- modules/pulsepointLiteBidAdapter.js | 36 ++++++-- .../modules/pulsepointLiteBidAdapter_spec.js | 89 ++++++++++++------- 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index da34de0fd26..6c74923f051 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -33,6 +33,7 @@ export const spec = { const request = { imp: bidRequest.map(slot => impression(slot)), site: site(bidRequest), + app: app(bidRequest), device: device(), }; return { @@ -197,13 +198,36 @@ function dataAsset(id, params, type, defaultLen) { */ function site(bidderRequest) { const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; - return { - publisher: { - id: pubId.toString(), - }, - ref: referrer(), - page: getTopWindowLocation().href, + const appParams = bidderRequest[0].params.app; + if(!appParams) { + return { + publisher: { + id: pubId.toString(), + }, + ref: referrer(), + page: getTopWindowLocation().href, + } }; + return null; +} + +/** + * Produces an OpenRTB App object. + */ +function app(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; + const appParams = bidderRequest[0].params.app; + if(appParams) { + return { + publisher: { + id: pubId.toString(), + }, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + }; + } + return null; } /** diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index 7899b6f7caa..6252ac55f91 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -5,41 +5,49 @@ import {getTopWindowLocation} from 'src/utils'; import {newBidder} from 'src/adapters/bidderFactory'; describe('PulsePoint Lite Adapter Tests', () => { - let slotConfigs; - let nativeSlotConfig; - - beforeEach(() => { - slotConfigs = [{ - placementCode: '/DfpAccount1/slot1', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '300x250' - } - }, { - placementCode: '/DfpAccount2/slot2', - bidId: 'bid23456', - params: { - cp: 'p10000', - ct: 't20000', - cf: '728x90' + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '300x250' + } + }, { + placementCode: '/DfpAccount2/slot2', + bidId: 'bid23456', + params: { + cp: 'p10000', + ct: 't20000', + cf: '728x90' + } + }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: { } + }, + params: { + cp: 'p10000', + ct: 't10000' + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + app: { + bundle: 'com.pulsepoint.apps', + storeUrl: 'http://pulsepoint.com/apps', + domain: 'pulsepoint.com', } - }]; - nativeSlotConfig = [{ - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - nativeParams: { - title: { required: true, len: 200 }, - image: { wmin: 100 }, - sponsoredBy: { } - }, - params: { - cp: 'p10000', - ct: 't10000' - } - }]; - }); + } + }]; it('Verify build request', () => { const request = spec.buildRequests(slotConfigs); @@ -210,4 +218,17 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(options[0].type).to.equal('iframe'); expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch'); }); + + it('Verify app requests', () => { + const request = spec.buildRequests(appSlotConfig); + const ortbRequest = JSON.parse(request.data); + // site object + expect(ortbRequest.site).to.equal(null); + expect(ortbRequest.app).to.not.be.null; + expect(ortbRequest.app.publisher).to.not.equal(null); + expect(ortbRequest.app.publisher.id).to.equal('p10000'); + expect(ortbRequest.app.bundle).to.equal('com.pulsepoint.apps'); + expect(ortbRequest.app.storeurl).to.equal('http://pulsepoint.com/apps'); + expect(ortbRequest.app.domain).to.equal('pulsepoint.com'); + }); }); From 5f3509c27b898a3ba9a1c7cd0cabd2c2c45d67d5 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Mon, 25 Sep 2017 23:43:01 +0530 Subject: [PATCH 09/12] fixing eslint issues --- modules/pulsepointLiteBidAdapter.js | 44 ++++++++----------- .../modules/pulsepointLiteBidAdapter_spec.js | 2 +- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index 6c74923f051..edeb6ebcc00 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -1,6 +1,4 @@ -import {createBid} from 'src/bidfactory'; import {logError, getTopWindowLocation} from 'src/utils'; -import {STATUS} from 'src/constants'; import { registerBidder } from 'src/adapters/bidderFactory'; const NATIVE_DEFAULTS = { @@ -25,12 +23,13 @@ export const spec = { supportedMediaTypes: ['native'], - isBidRequestValid: bid => { - return !!(bid && bid.params && bid.params.cp && bid.params.ct); - }, + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.cp && bid.params.ct) + ), buildRequests: bidRequest => { const request = { + id: bidRequest[0].bidderRequestId, imp: bidRequest.map(slot => impression(slot)), site: site(bidRequest), app: app(bidRequest), @@ -43,9 +42,9 @@ export const spec = { }; }, - interpretResponse: (response, request) => { - return bidResponseAvailable(request, response); - }, + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response) + ), getUserSyncs: syncOptions => { if (syncOptions.iframeEnabled) { @@ -66,16 +65,16 @@ function bidResponseAvailable(bidRequest, bidResponse) { const idToBidMap = {}; // extract the request bids and the response bids, keyed by impr-id const ortbRequest = parse(bidRequest.data); - ortbRequest.imp.forEach((imp) => { + ortbRequest.imp.forEach(imp => { idToImpMap[imp.id] = imp; }); if (bidResponse) { - bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach((bid) => { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { idToBidMap[bid.impid] = bid; })); } const bids = []; - Object.keys(idToImpMap).forEach((id) => { + Object.keys(idToImpMap).forEach(id => { if (idToBidMap[id]) { const bid = { requestId: id, @@ -154,7 +153,7 @@ function addAsset(assets, asset) { function titleAsset(id, params, defaultLen) { if (params) { return { - id: id, + id, required: params.required ? 1 : 0, title: { len: params.len || defaultLen, @@ -169,7 +168,7 @@ function titleAsset(id, params, defaultLen) { */ function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { return params ? { - id: id, + id, required: params.required ? 1 : 0, img: { type, @@ -184,7 +183,7 @@ function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { */ function dataAsset(id, params, type, defaultLen) { return params ? { - id: id, + id, required: params.required ? 1 : 0, data: { type, @@ -199,7 +198,7 @@ function dataAsset(id, params, type, defaultLen) { function site(bidderRequest) { const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; const appParams = bidderRequest[0].params.app; - if(!appParams) { + if (!appParams) { return { publisher: { id: pubId.toString(), @@ -207,7 +206,7 @@ function site(bidderRequest) { ref: referrer(), page: getTopWindowLocation().href, } - }; + } return null; } @@ -217,7 +216,7 @@ function site(bidderRequest) { function app(bidderRequest) { const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; const appParams = bidderRequest[0].params.app; - if(appParams) { + if (appParams) { return { publisher: { id: pubId.toString(), @@ -225,7 +224,7 @@ function app(bidderRequest) { bundle: appParams.bundle, storeurl: appParams.storeUrl, domain: appParams.domain, - }; + } } return null; } @@ -287,7 +286,7 @@ function nativeResponse(imp, bid) { const nativeAd = parse(bid.adm); const keys = {}; if (nativeAd && nativeAd.native && nativeAd.native.assets) { - nativeAd.native.assets.forEach((asset) => { + nativeAd.native.assets.forEach(asset => { keys.title = asset.title ? asset.title.text : keys.title; keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body; keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy; @@ -304,11 +303,4 @@ function nativeResponse(imp, bid) { return null; } -/** - * Parses the native response from the Bid given. - */ -function isNative(slot) { - return !!slot.nativeParams; -} - registerBidder(spec); diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index 6252ac55f91..0573b9797fd 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -155,7 +155,7 @@ describe('PulsePoint Lite Adapter Tests', () => { { img: { type: 3, url: 'http://images.cdn.brand.com/123' } } ], link: { url: 'http://brand.clickme.com/' }, - imptrackers: [ 'http://imp1.trackme.com/', 'http://imp1.contextweb.com/' ] + imptrackers: ['http://imp1.trackme.com/', 'http://imp1.contextweb.com/'] } }; const ortbResponse = { From f9a818c958b9c4049f648d9faab1a67756d39433 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Wed, 27 Sep 2017 21:25:59 +0530 Subject: [PATCH 10/12] adding adapter documentation --- modules/pulsepointLiteBidAdapter.js | 1 + modules/pulsepointLiteBidAdapter.md | 43 +++++++++++++++++++ .../modules/pulsepointLiteBidAdapter_spec.js | 2 + 3 files changed, 46 insertions(+) create mode 100644 modules/pulsepointLiteBidAdapter.md diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index edeb6ebcc00..517c551b21a 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -80,6 +80,7 @@ function bidResponseAvailable(bidRequest, bidResponse) { requestId: id, cpm: idToBidMap[id].price, creative_id: id, + creativeId: id, adId: id, }; if (idToImpMap[id].native) { diff --git a/modules/pulsepointLiteBidAdapter.md b/modules/pulsepointLiteBidAdapter.md new file mode 100644 index 00000000000..7b071237692 --- /dev/null +++ b/modules/pulsepointLiteBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +Module Name: PulsePoint Lite Bidder Adapter +Module Type: Bidder Adapter +Maintainer: ExchangeTeam@pulsepoint.com + +# Description + +Connects to PulsePoint demand source to fetch bids. +Banner, Outstream and Native formats are supported. +Please use ```pulseLite``` as the bidder code. + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'pulsepointLite', + params: { + cf: '300X250', + cp: 512379, + ct: 486653 + } + }] + },{ + code: 'native-ad-div', + sizes: [[1, 1]], + nativeParams: { + title: { required: true, len: 75 }, + image: { required: true }, + body: { len: 200 }, + sponsoredBy: { len: 20 } + }, + bids: [{ + bidder: 'pulseLite', + params: { + cp: 512379, + ct: 505642 + } + }] + }]; +``` diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index 0573b9797fd..fb505cabfeb 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -97,6 +97,8 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.adId).to.equal('bid12345'); + expect(bid.creative_id).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); }); it('Verify full passback', () => { From 403ef2e53e8d4597bfa2ff754eb6934eb9069750 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Wed, 27 Sep 2017 21:28:20 +0530 Subject: [PATCH 11/12] minor doc update --- modules/pulsepointLiteBidAdapter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pulsepointLiteBidAdapter.md b/modules/pulsepointLiteBidAdapter.md index 7b071237692..23c96758ca0 100644 --- a/modules/pulsepointLiteBidAdapter.md +++ b/modules/pulsepointLiteBidAdapter.md @@ -1,8 +1,8 @@ # Overview -Module Name: PulsePoint Lite Bidder Adapter -Module Type: Bidder Adapter -Maintainer: ExchangeTeam@pulsepoint.com +**Module Name**: PulsePoint Lite Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: ExchangeTeam@pulsepoint.com # Description From 8676e5ef3d0e42a4892bab3b205ea06f36071597 Mon Sep 17 00:00:00 2001 From: avenkatraman Date: Thu, 28 Sep 2017 20:55:39 +0530 Subject: [PATCH 12/12] removing usage of reserved keyword 'native' --- modules/pulsepointLiteBidAdapter.js | 2 +- .../modules/pulsepointLiteBidAdapter_spec.js | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js index e528ca4cd99..84f955317c0 100644 --- a/modules/pulsepointLiteBidAdapter.js +++ b/modules/pulsepointLiteBidAdapter.js @@ -84,7 +84,7 @@ function bidResponseAvailable(bidRequest, bidResponse) { creativeId: id, adId: id, }; - if (idToImpMap[id].native) { + if (idToImpMap[id]['native']) { bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); bid.mediaType = 'native'; } else { diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js index fb505cabfeb..8e1f12dac93 100644 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ b/test/spec/modules/pulsepointLiteBidAdapter_spec.js @@ -1,3 +1,4 @@ +/* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; import {spec} from 'modules/pulsepointLiteBidAdapter'; import bidManager from 'src/bidmanager'; @@ -115,11 +116,12 @@ describe('PulsePoint Lite Adapter Tests', () => { // native impression expect(ortbRequest.imp[0].tagid).to.equal('t10000'); expect(ortbRequest.imp[0].banner).to.equal(null); - expect(ortbRequest.imp[0].native).to.not.equal(null); - expect(ortbRequest.imp[0].native.ver).to.equal('1.1'); - expect(ortbRequest.imp[0].native.request).to.not.equal(null); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); // native request assets - const nativeRequest = JSON.parse(ortbRequest.imp[0].native.request); + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); expect(nativeRequest).to.not.equal(null); expect(nativeRequest.assets).to.have.lengthOf(3); // title asset @@ -150,7 +152,7 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(request.method).to.equal('POST'); const ortbRequest = JSON.parse(request.data); const nativeResponse = { - native: { + 'native': { assets: [ { title: { text: 'Ad Title'} }, { data: { type: 1, value: 'Sponsored By: Brand' }}, @@ -176,14 +178,15 @@ describe('PulsePoint Lite Adapter Tests', () => { expect(bid.adId).to.equal('bid12345'); expect(bid.ad).to.be.undefined; expect(bid.mediaType).to.equal('native'); - expect(bid.native).to.not.equal(null); - expect(bid.native.title).to.equal('Ad Title'); - expect(bid.native.sponsoredBy).to.equal('Sponsored By: Brand'); - expect(bid.native.image).to.equal('http://images.cdn.brand.com/123'); - expect(bid.native.clickUrl).to.equal(encodeURIComponent('http://brand.clickme.com/')); - expect(bid.native.impressionTrackers).to.have.lengthOf(2); - expect(bid.native.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); - expect(bid.native.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('Sponsored By: Brand'); + expect(nativeBid.image).to.equal('http://images.cdn.brand.com/123'); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.clickme.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(2); + expect(nativeBid.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); + expect(nativeBid.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); }); it('Verifies bidder code', () => {