-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TapSense Header Bidding Adapter and tests (#1004)
* Add TapSense Header Bidding Adapter and tests * Update for Tapsense Prebid Header * changed var to es6 let/const * when checking for bid sizes, use utils.parseSizesInput to handle single/nested arrays * use template strings where applicable * use $$PREBID_GLOBAL$$ instead of window * scriptUrl is now static * named anonymous function in generateCallBack * add more tests in tapsense_spec.js * Url Callback parameter needed prebid global object
- Loading branch information
Showing
3 changed files
with
348 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,7 @@ | |
"vertoz", | ||
"widespace", | ||
"admixer", | ||
"tapsense", | ||
{ | ||
"appnexus": { | ||
"alias": "brealtime" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
//v0.0.1 | ||
|
||
const bidfactory = require('../bidfactory.js'); | ||
const bidmanager = require('../bidmanager.js'); | ||
const adloader = require('../adloader'); | ||
const utils = require('../utils.js'); | ||
|
||
const TapSenseAdapter = function TapSenseAdapter() { | ||
const version = "0.0.1"; | ||
const creativeSizes = [ | ||
"320x50" | ||
]; | ||
const validParams = [ | ||
"ufid", | ||
"refer", | ||
"ad_unit_id", //required | ||
"device_id", | ||
"lat", | ||
"long", | ||
"user", //required | ||
"price_floor", | ||
"test" | ||
]; | ||
const SCRIPT_URL = "https://ads04.tapsense.com/ads/headerad"; | ||
let bids; | ||
$$PREBID_GLOBAL$$.tapsense = {}; | ||
function _callBids(params) { | ||
bids = params.bids || []; | ||
for (let i = 0; i < bids.length; i++) { | ||
let bid = bids[i]; | ||
let isValidSize = false; | ||
if (!bid.sizes || !bid.params.user || !bid.params.ad_unit_id) { | ||
return; | ||
} | ||
let parsedSizes = utils.parseSizesInput(bid.sizes); | ||
for (let k = 0; k < parsedSizes.length; k++) { | ||
if (creativeSizes.indexOf(parsedSizes[k]) > -1) { | ||
isValidSize = true; | ||
break; | ||
} | ||
} | ||
if (isValidSize) { | ||
let queryString = `?price=true&jsonp=1&callback=$$PREBID_GLOBAL$$.tapsense.callback_with_price_${bid.bidId}&version=${version}&`; | ||
$$PREBID_GLOBAL$$.tapsense[`callback_with_price_${bid.bidId}`] = generateCallback(bid.bidId); | ||
let keys = Object.keys(bid.params); | ||
for (let j = 0; j < keys.length; j++) { | ||
if (validParams.indexOf(keys[j]) < 0) continue; | ||
queryString += encodeURIComponent(keys[j]) + "=" + encodeURIComponent(bid.params[keys[j]]) + "&"; | ||
} | ||
_requestBids(SCRIPT_URL + queryString); | ||
} | ||
} | ||
} | ||
|
||
function generateCallback(bidId){ | ||
return function tapsenseCallback(response, price) { | ||
let bidObj; | ||
if (response && price) { | ||
let bidReq = utils.getBidRequest(bidId); | ||
if (response.status.value === "ok" && response.count_ad_units > 0) { | ||
bidObj = bidfactory.createBid(1, bidObj); | ||
bidObj.cpm = price; | ||
bidObj.width = response.width; | ||
bidObj.height = response.height; | ||
bidObj.ad = response.ad_units[0].html; | ||
} else { | ||
bidObj = bidfactory.createBid(2, bidObj); | ||
} | ||
bidObj.bidderCode = bidReq.bidder; | ||
bidmanager.addBidResponse(bidReq.placementCode, bidObj); | ||
|
||
} else { | ||
utils.logMessage('No prebid response'); | ||
} | ||
}; | ||
} | ||
|
||
function _requestBids(scriptURL) { | ||
adloader.loadScript(scriptURL); | ||
} | ||
|
||
return { | ||
callBids: _callBids | ||
}; | ||
}; | ||
|
||
module.exports = TapSenseAdapter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
import { expect } from 'chai'; | ||
import Adapter from 'src/adapters/tapsense'; | ||
import bidmanager from 'src/bidmanager'; | ||
import adloader from "src/adloader"; | ||
import * as utils from "src/utils"; | ||
|
||
window.pbjs = window.pbjs || {}; | ||
|
||
const DEFAULT_BIDDER_REQUEST = { | ||
"bidderCode": "tapsense", | ||
"bidderRequestId": "141ed07a281ca3", | ||
"requestId": "b202e550-b0f7-4fb9-bfb4-1aa80f1795b4", | ||
"start": new Date().getTime(), | ||
"bids": [ | ||
{ | ||
"sizes": undefined, //set values in tests | ||
"bidder": "tapsense", | ||
"bidId": "2b211418dd0575", | ||
"bidderRequestId": "141ed07a281ca3", | ||
"placementCode": "thisisatest", | ||
"params": { | ||
"ufid": "thisisaufid", | ||
"refer": "thisisarefer", | ||
"version": "0.0.1", | ||
"ad_unit_id": "thisisanadunitid", | ||
"device_id": "thisisadeviceid", | ||
"lat": "thisislat", | ||
"long": "thisisalong", | ||
"user": "thisisanidfa", | ||
"price_floor": 0.01 | ||
} | ||
} | ||
] | ||
} | ||
|
||
const SUCCESSFUL_RESPONSE = { | ||
"count_ad_units": 1, | ||
"status": { | ||
"value": "ok", | ||
}, | ||
"ad_units": [ | ||
{ | ||
html: "<html><head></head><body></body></html>", | ||
imp_url: "https://i.tapsense.com" | ||
} | ||
], | ||
"id": "thisisanid", | ||
"width": 320, | ||
"height": 50, | ||
"time": new Date().getTime() | ||
} | ||
|
||
const UNSUCCESSFUL_RESPONSE = { | ||
"count_ad_units": 0, | ||
"status": { | ||
"value": "nofill" //will be set in test | ||
}, | ||
"time": new Date().getTime() | ||
} | ||
|
||
function duplicate(obj) { | ||
return JSON.parse(JSON.stringify(obj)); | ||
} | ||
|
||
function makeSuccessfulRequest(adapter){ | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = [[320,50], [500,500]]; | ||
adapter.callBids(modifiedReq); | ||
return modifiedReq.bids; | ||
} | ||
|
||
describe ("TapSenseAdapter", () => { | ||
let adapter, sandbox; | ||
beforeEach(() => { | ||
adapter = new Adapter; | ||
sandbox = sinon.sandbox.create(); | ||
}); | ||
afterEach(() => { | ||
sandbox.restore(); | ||
}) | ||
|
||
describe('request function', () => { | ||
beforeEach(() => { | ||
sandbox.stub(adloader, 'loadScript'); | ||
}); | ||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
it('exists and is a function', () => { | ||
expect(adapter.callBids).to.exist.and.to.be.a('function'); | ||
}); | ||
it('requires parameters to make request', () => { | ||
adapter.callBids({}); | ||
sinon.assert.notCalled(adloader.loadScript); | ||
}); | ||
it('does not make a request if missing user', () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
delete modifiedReq.bids.user | ||
adapter.callBids(modifiedReq); | ||
sinon.assert.notCalled(adloader.loadScript); | ||
}); | ||
it('does not make a request if missing ad_unit_id', () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
delete modifiedReq.bids.ad_unit_id | ||
adapter.callBids(modifiedReq); | ||
sinon.assert.notCalled(adloader.loadScript); | ||
}); | ||
it('does not make a request if ad sizes are incorrect', () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = [[500,500]]; | ||
adapter.callBids(modifiedReq); | ||
sinon.assert.notCalled(adloader.loadScript); | ||
}); | ||
it('does not make a request if ad sizes are invalid format', () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = 1234; | ||
adapter.callBids(modifiedReq); | ||
sinon.assert.notCalled(adloader.loadScript); | ||
}); | ||
|
||
describe("requesting an ad", () => { | ||
afterEach(() => { | ||
sandbox.restore(); | ||
}) | ||
it("makes a request if valid sizes are provided (nested array)", () => { | ||
makeSuccessfulRequest(adapter); | ||
sinon.assert.calledOnce(adloader.loadScript); | ||
expect(adloader.loadScript.firstCall.args[0]).to.contain( | ||
"ads04.tapsense.com" | ||
); | ||
}); | ||
it("handles a singles array for size parameter", () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = [320,50]; | ||
adapter.callBids(modifiedReq); | ||
expect(adloader.loadScript.firstCall.args[0]).to.contain( | ||
"ads04.tapsense.com" | ||
); | ||
}); | ||
it("handles a string for size parameter", () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = "320x50"; | ||
adapter.callBids(modifiedReq); | ||
expect(adloader.loadScript.firstCall.args[0]).to.contain( | ||
"ads04.tapsense.com" | ||
); | ||
}); | ||
it("handles a string with multiple sizes for size parameter", () => { | ||
let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); | ||
modifiedReq.bids[0].sizes = "320x50,500x500"; | ||
adapter.callBids(modifiedReq); | ||
expect(adloader.loadScript.firstCall.args[0]).to.contain( | ||
"ads04.tapsense.com" | ||
); | ||
}); | ||
it("appends bid params as a query string when requesting ad", () => { | ||
makeSuccessfulRequest(adapter); | ||
sinon.assert.calledOnce(adloader.loadScript); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/ufid=thisisaufid&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/refer=thisisarefer&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/version=[^&]+&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/jsonp=1&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/ad_unit_id=thisisanadunitid&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/device_id=thisisadeviceid&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/lat=thisislat&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/long=thisisalong&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/user=thisisanidfa&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/price_floor=0\.01&/ | ||
); | ||
expect(adloader.loadScript.firstCall.args[0]).to.match( | ||
/callback=pbjs\.tapsense\.callback_with_price_.+&/ | ||
); | ||
}) | ||
}) | ||
}); | ||
|
||
describe("generateCallback", () => { | ||
beforeEach(() => { | ||
sandbox.stub(adloader, 'loadScript'); | ||
}); | ||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
it("generates callback in namespaced object with correct bidder id", () => { | ||
makeSuccessfulRequest(adapter); | ||
expect(pbjs.tapsense.callback_with_price_2b211418dd0575).to.exist.and.to.be.a('function'); | ||
}) | ||
}); | ||
|
||
describe("response", () => { | ||
beforeEach(() => { | ||
sandbox.stub(bidmanager, 'addBidResponse'); | ||
sandbox.stub(adloader, 'loadScript'); | ||
let bids = makeSuccessfulRequest(adapter); | ||
sandbox.stub(utils, "getBidRequest", (id) => { | ||
return bids.find((item) => { return item.bidId === id}); | ||
}) | ||
}); | ||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
describe("successful response", () => { | ||
beforeEach(() => { | ||
pbjs.tapsense.callback_with_price_2b211418dd0575(SUCCESSFUL_RESPONSE, 1.2); | ||
}); | ||
it("called the bidmanager and registers a bid", () => { | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(1); | ||
}); | ||
it("should have the correct placementCode", () => { | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal("thisisatest"); | ||
}); | ||
}); | ||
describe("unsuccessful response", () => { | ||
beforeEach(() => { | ||
pbjs.tapsense.callback_with_price_2b211418dd0575(UNSUCCESSFUL_RESPONSE, 1.2); | ||
}) | ||
it("should call the bidmanger and register an invalid bid", () => { | ||
sinon.assert.calledOnce(bidmanager.addBidResponse); | ||
expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); | ||
}); | ||
it("should have the correct placementCode", () => { | ||
expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal("thisisatest"); | ||
}) | ||
}); | ||
describe("no response/timeout", () => { | ||
it("should not register any bids", () => { | ||
sinon.assert.notCalled(bidmanager.addBidResponse); | ||
}) | ||
}); | ||
describe("edge cases", () => { | ||
it("does not register a bid if no price is supplied", () => { | ||
sandbox.stub(utils, "logMessage"); | ||
pbjs.tapsense.callback_with_price_2b211418dd0575(SUCCESSFUL_RESPONSE); | ||
sinon.assert.notCalled(bidmanager.addBidResponse); | ||
}); | ||
}); | ||
}); | ||
|
||
}) |