Skip to content

Commit

Permalink
Add TapSense Header Bidding Adapter and tests (#1004)
Browse files Browse the repository at this point in the history
* 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
erikchau authored and Matt Kendall committed Mar 8, 2017
1 parent de8bc31 commit c8b77a3
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 0 deletions.
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"vertoz",
"widespace",
"admixer",
"tapsense",
{
"appnexus": {
"alias": "brealtime"
Expand Down
87 changes: 87 additions & 0 deletions src/adapters/tapsense.js
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;
260 changes: 260 additions & 0 deletions test/spec/adapters/tapsense_spec.js
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);
});
});
});

})

0 comments on commit c8b77a3

Please sign in to comment.