Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AerServ Adapter #1538

Merged
merged 9 commits into from
Aug 31, 2017
117 changes: 117 additions & 0 deletions modules/aerservBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import bidfactory from 'src/bidfactory';
import bidmanager from 'src/bidmanager';
import * as utils from 'src/utils';
import {ajax} from 'src/ajax';
import {STATUS} from 'src/constants';
import adaptermanager from 'src/adaptermanager';

const BIDDER_CODE = 'aerserv';

const AerServAdapter = function AerServAdapter() {
const ENVIRONMENTS = {
local: '127.0.0.1:8080',
dev: 'dev-ads.aerserv.com',
stage: 'staging-ads.aerserv.com',
prod: 'ads.aerserv.com'
};

const BANNER_PATH = '/as/json/pbjs/v1?';
const VIDEO_PATH = '/as/json/pbjsvast/v1?';
const REQUIRED_PARAMS = ['plc'];

function _isResponseValid(bidRequest, response) {
return ((bidRequest.mediaType === 'video' && response.vastUrl) || (bidRequest.mediaType !== 'video' && response.adm)) &&
response.cpm && response.cpm > 0;
}

function _createBid(bidRequest, response) {
if (_isResponseValid(bidRequest, response)) {
let bid = bidfactory.createBid(1, bidRequest);
bid.bidderCode = BIDDER_CODE;
bid.cpm = response.cpm;
bid.width = response.w;
bid.height = response.h;
if (bidRequest.mediaType === 'video') {
bid.vastUrl = response.vastUrl;
bid.descriptionUrl = response.vastUrl;
bid.mediaType = 'video';
} else {
bid.ad = response.adm;
}
bidmanager.addBidResponse(bidRequest.placementCode, bid);
} else {
bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest));
}
}

function _getFirstSize(sizes) {
let sizeObj = {};
if (utils.isArray(sizes) && sizes.length > 0 && utils.isArray(sizes[0]) && sizes[0].length === 2) {
sizeObj['vpw'] = sizes[0][0];
sizeObj['vph'] = sizes[0][1];
}
return sizeObj;
}

function _buildQueryParameters(bid, requestParams) {
Object.keys(bid.params).filter(param => param !== 'video')
.forEach(param => requestParams[param] = bid.params[param]);

if (bid.mediaType === 'video') {
let videoDimensions = _getFirstSize(bid.sizes);
Object.keys(videoDimensions).forEach(param => requestParams[param] = videoDimensions[param]);
Object.keys(bid.params.video || {}).forEach(param => requestParams[param] = bid.params.video[param]);
}

return utils.parseQueryStringParameters(requestParams);
}

function _handleResponse(bidRequest) {
return response => {
if (!response && response.length <= 0) {
bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest));
utils.logError('Empty response');
return;
}

try {
response = JSON.parse(response);
} catch (e) {
bidmanager.addBidResponse(bidRequest.placementCode, bidfactory.createBid(STATUS.NO_BID, bidRequest));
utils.logError('Invalid JSON in response');
return;
}

_createBid(bidRequest, response);
};
}

function _callBids(bidRequests) {
let currentUrl = (window.parent !== window) ? document.referrer : window.location.href;
currentUrl = currentUrl && encodeURIComponent(currentUrl);

let bids = bidRequests.bids || [];
bids.forEach(bid => {
if (utils.hasValidBidRequest(bid.params, REQUIRED_PARAMS, BIDDER_CODE)) {
let env = ENVIRONMENTS[bid.params['env']] || ENVIRONMENTS['prod'];
let requestPath = bid.mediaType === 'video' ? VIDEO_PATH : BANNER_PATH;
let pageParameters = {url: currentUrl};
let parameterStr = _buildQueryParameters(bid, pageParameters);

let url = `//${env}${requestPath}${parameterStr}`;
utils.logMessage('sending request to: ' + url);
ajax(url, _handleResponse(bid), null, {withCredentials: true});
} else {
bidmanager.addBidResponse(bid.placementCode, bidfactory.createBid(STATUS.NO_BID, bid));
}
});
}

return {
callBids: _callBids
}
};

adaptermanager.registerBidAdapter(new AerServAdapter(), BIDDER_CODE, {supportedMediaTypes: ['video']});

module.exports = AerServAdapter;
213 changes: 213 additions & 0 deletions test/spec/modules/aerservBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import {expect} from 'chai';
import AerServAdapter from 'modules/aerservBidAdapter';
import bidmanager from 'src/bidmanager';

const BASE_REQUEST = JSON.stringify({
bidderCode: 'aerserv',
requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53',
bidderRequestId: '1f8c8c03de01f9',
bids: [
{
bidder: 'aerserv',
params: {
plc: '480',
},
placementCode: 'adunit-1',
transactionId: 'a0e033af-f50c-4a7e-aeed-c01c5f709848',
sizes: [[300, 250], [300, 600]],
bidId: '2f4a69463b3bc9',
bidderRequestId: '1f8c8c03de01f9',
requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53'
}
]
});

describe('AerServ Adapter', () => {
let adapter;
let bidmanagerStub;

beforeEach(() => {
adapter = new AerServAdapter();
bidmanagerStub = sinon.stub(bidmanager, 'addBidResponse');
});

afterEach(() => {
bidmanager.addBidResponse.restore();
});

describe('callBids()', () => {
let xhr;
let requests;

beforeEach(() => {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = request => requests.push(request);
});

afterEach(() => {
xhr.restore();
});

it('exists and is a function', () => {
expect(adapter.callBids).to.exist.and.to.be.a('function');
});

it('should not add bid responses with no bids to call', () => {
adapter.callBids({});

sinon.assert.notCalled(bidmanager.addBidResponse);
});

it('requires plc parameter to make request', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0].params = {};
adapter.callBids(bidRequest);
expect(requests).to.be.empty;
});

it('sends requests to normal endpoint for non-video requests', () => {
adapter.callBids(JSON.parse(BASE_REQUEST));
expect(requests.length).to.equal(1);
expect(requests[0].url).to.include('/as/json/pbjs/v1');
});

it('sends requests to video endpoint for video requests', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0]['mediaType'] = 'video';
bidRequest.bids[0]['video'] = {};
adapter.callBids(bidRequest);
expect(requests[0].url).to.include('/as/json/pbjsvast/v1');
});

it('properly adds video parameters to the request', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0]['mediaType'] = 'video';
bidRequest.bids[0].params['video'] = { videoParam: 'videoValue' };
adapter.callBids(bidRequest);
expect(requests[0].url).to.include('videoParam=videoValue');
});

it('parses the first size for video requests', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0]['mediaType'] = 'video';
adapter.callBids(bidRequest);
expect(requests[0].url).to.include('vpw=300');
expect(requests[0].url).to.include('vph=250');
});

it('sends requests to production by default', () => {
adapter.callBids(JSON.parse(BASE_REQUEST));
expect(requests[0].url).to.include('//ads.aerserv.com');
});

it('sends requests to the specified endpoint when \'env\' is provided', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0].params['env'] = 'dev';
adapter.callBids(bidRequest);
expect(requests[0].url).to.include('//dev-ads.aerserv.com');
});
});

describe('response handling', () => {
let server;

beforeEach(() => {
server = sinon.fakeServer.create();
});

afterEach(() => {
server.restore();
});

it('responds with an empty bid without required parameters', () => {
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0].params = {};
adapter.callBids(bidRequest);
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

it('responds with an empty bid on empty response', () => {
server.respondWith('');

adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

it('responds with an empty bid on un-parseable JSON response', () => {
server.respondWith('{\"bad\":\"json}');

adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

it('responds with a valid bid returned ad', () => {
server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, adm: 'sweet ad markup'}));
adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(1);
});

it('responds with a valid bid from returned ad', () => {
server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, vastUrl: 'sweet URL where VAST is at'}));
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0]['mediaType'] = 'video';
bidRequest.bids[0]['video'] = {};
adapter.callBids(bidRequest);
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(1);
});

it('responds with empty bid if response has no ad', () => {
server.respondWith(JSON.stringify({error: 'no ads'}));
adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

// things that should never occur
it('responds with empty bid if response has 0 or below cpm', () => {
server.respondWith(JSON.stringify({cpm: 0, w: 320, h: 50, adm: 'sweet ad markup'}));
adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

it('responds with empty bid if response has no markup', () => {
server.respondWith(JSON.stringify({cpm: 5.0, w: 320, h: 50}));
adapter.callBids(JSON.parse(BASE_REQUEST));
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});

it('responds with an empty bid if response has no video markup', () => {
server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50}));
let bidRequest = JSON.parse(BASE_REQUEST);
bidRequest.bids[0]['mediaType'] = 'video';
bidRequest.bids[0]['video'] = {};
adapter.callBids(bidRequest);
server.respond();
let bid = bidmanagerStub.getCall(0).args[1];
sinon.assert.calledOnce(bidmanager.addBidResponse);
expect(bid.getStatusCode()).to.equal(2);
});
});
});