Skip to content

Commit

Permalink
Adxcg adapter (prebid#1554)
Browse files Browse the repository at this point in the history
* added adxcg adapter and associated tests

* Removed unnecessary string concatenations and similar utilitiy functions and replaced them with those from prebid utils.js and url.js.
Enabled credentials in the ajax call.
Replaced string concatenations with template literals where possible.
Escaped native click url in order to correctly handle redirects.
  • Loading branch information
adxcgcom authored and philipwatson committed Sep 18, 2017
1 parent 3890dcf commit a7335be
Show file tree
Hide file tree
Showing 2 changed files with 351 additions and 0 deletions.
139 changes: 139 additions & 0 deletions modules/adxcgBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import bidfactory from 'src/bidfactory';
import bidmanager from 'src/bidmanager';
import * as utils from 'src/utils';
import { STATUS } from 'src/constants';
import adaptermanager from 'src/adaptermanager';
import { ajax } from 'src/ajax';
import * as url from 'src/url';

/**
* Adapter for requesting bids from Adxcg
* updated from latest prebid repo on 2017.08.30
*/
function AdxcgAdapter() {
let bidRequests = {};

function _callBids(params) {
if (params.bids && params.bids.length > 0) {
let adZoneIds = [];
let prebidBidIds = [];
let sizes = [];

params.bids.forEach(bid => {
bidRequests[bid.bidId] = bid;
adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params));
prebidBidIds.push(bid.bidId);
sizes.push(utils.parseSizesInput(bid.sizes).join('|'));
});

let location = utils.getTopWindowLocation();
let secure = location.protocol == 'https:';

let requestUrl = url.parse(location.href);
requestUrl.search = null;
requestUrl.hash = null;

let adxcgRequestUrl = url.format({
protocol: secure ? 'https' : 'http',
hostname: secure ? 'ad-emea-secure.adxcg.net' : 'ad-emea.adxcg.net',
pathname: '/get/adi',
search: {
renderformat: 'javascript',
ver: 'r20141124',
adzoneid: adZoneIds.join(','),
format: sizes.join(','),
prebidBidIds: prebidBidIds.join(','),
url: escape(url.format(requestUrl)),
secure: secure ? '1' : '0'
}
});

utils.logMessage(`submitting request: ${adxcgRequestUrl}`);
ajax(adxcgRequestUrl, handleResponse, null, {
withCredentials: true
});
}
}

function handleResponse(response) {
let adxcgBidReponseList;

try {
adxcgBidReponseList = JSON.parse(response);
utils.logMessage(`adxcgBidReponseList: ${JSON.stringify(adxcgBidReponseList)}`);
} catch (error) {
adxcgBidReponseList = [];
utils.logError(error);
}

adxcgBidReponseList.forEach(adxcgBidReponse => {
let bidRequest = bidRequests[adxcgBidReponse.bidId];
delete bidRequests[adxcgBidReponse.bidId];

let bid = bidfactory.createBid(STATUS.GOOD, bidRequest);

bid.creative_id = adxcgBidReponse.creativeId;
bid.code = 'adxcg';
bid.bidderCode = 'adxcg';
bid.cpm = adxcgBidReponse.cpm;

if (adxcgBidReponse.ad) {
bid.ad = adxcgBidReponse.ad;
} else if (adxcgBidReponse.vastUrl) {
bid.vastUrl = adxcgBidReponse.vastUrl;
bid.descriptionUrl = adxcgBidReponse.vastUrl;
bid.mediaType = 'video';
} else if (adxcgBidReponse.nativeResponse) {
bid.mediaType = 'native';

let nativeResponse = adxcgBidReponse.nativeResponse;

bid.native = {
clickUrl: escape(nativeResponse.link.url),
impressionTrackers: nativeResponse.imptrackers
};

nativeResponse.assets.forEach(asset => {
if (asset.title && asset.title.text) {
bid.native.title = asset.title.text;
}

if (asset.img && asset.img.url) {
bid.native.image = asset.img.url;
}

if (asset.data && asset.data.label == 'DESC' && asset.data.value) {
bid.native.body = asset.data.value;
}

if (asset.data && asset.data.label == 'SPONSORED' && asset.data.value) {
bid.native.sponsoredBy = asset.data.value;
}
});
}

bid.width = adxcgBidReponse.width;
bid.height = adxcgBidReponse.height;

utils.logMessage(`submitting bid[${bidRequest.placementCode}]: ${JSON.stringify(bid)}`);
bidmanager.addBidResponse(bidRequest.placementCode, bid);
});

Object.keys(bidRequests)
.map(bidId => bidRequests[bidId].placementCode)
.forEach(placementCode => {
utils.logMessage(`creating no_bid bid for: ${placementCode}`);
bidmanager.addBidResponse(placementCode, bidfactory.createBid(STATUS.NO_BID));
});
};

return {
callBids: _callBids
};
};

adaptermanager.registerBidAdapter(new AdxcgAdapter(), 'adxcg', {
supportedMediaTypes: ['video', 'native']
});

module.exports = AdxcgAdapter;
212 changes: 212 additions & 0 deletions test/spec/modules/adxcgBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { expect } from 'chai';
import Adapter from 'modules/adxcgBidAdapter';
import bidmanager from 'src/bidmanager';
import * as url from 'src/url';

const REQUEST = {
'bidderCode': 'adxcg',
'bids': [
{
'bidder': 'adxcg',
'params': {
'adzoneid': '1',
},
'sizes': [
[300, 250],
[640, 360],
[1, 1]
],
'bidId': '84ab500420319d',
'bidderRequestId': '7101db09af0db2'
}
]
};

const RESPONSE = [{
'bidId': '84ab500420319d',
'width': 300,
'height': 250,
'creativeId': '42',
'cpm': 0.45,
'ad': '<!-- adContent -->'
}]

const VIDEO_RESPONSE = [{
'bidId': '84ab500420319d',
'width': 640,
'height': 360,
'creativeId': '42',
'cpm': 0.45,
'vastUrl': 'vastContentUrl'
}]

const NATIVE_RESPONSE = [{
'bidId': '84ab500420319d',
'width': 0,
'height': 0,
'creativeId': '42',
'cpm': 0.45,
'nativeResponse': {
'assets': [{
'id': 1,
'required': 0,
'title': {
'text': 'titleContent'
}
}, {
'id': 2,
'required': 0,
'img': {
'url': 'imageContent',
'w': 600,
'h': 600
}
}, {
'id': 3,
'required': 0,
'data': {
'label': 'DESC',
'value': 'descriptionContent'
}
}, {
'id': 0,
'required': 0,
'data': {
'label': 'SPONSORED',
'value': 'sponsoredByContent'
}
}],
'link': {
'url': 'linkContent'
},
'imptrackers': ['impressionTracker1', 'impressionTracker2']
}
}]

describe('AdxcgAdapter', () => {
let adapter;

beforeEach(() => adapter = new Adapter());

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

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

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

it('creates a valid adxcg request url', () => {
adapter.callBids(REQUEST);

let parsedRequestUrl = url.parse(requests[0].url);

expect(parsedRequestUrl.hostname).to.equal('ad-emea.adxcg.net');
expect(parsedRequestUrl.pathname).to.equal('/get/adi');

let query = parsedRequestUrl.search;
expect(query.renderformat).to.equal('javascript');
expect(query.ver).to.equal('r20141124');
expect(query.adzoneid).to.equal('1');
expect(query.format).to.equal('300x250|640x360|1x1');
expect(query.jsonp).to.be.empty;
expect(query.prebidBidIds).to.equal('84ab500420319d');
});
});

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

beforeEach(() => {
server = sinon.fakeServer.create();
sinon.stub(bidmanager, 'addBidResponse');
});

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

it('handles regular responses', () => {
server.respondWith(JSON.stringify(RESPONSE));

adapter.callBids(REQUEST);
server.respond();
sinon.assert.calledOnce(bidmanager.addBidResponse);

const bidResponse = bidmanager.addBidResponse.firstCall.args[1];
expect(bidResponse.bidderCode).to.equal('adxcg');
expect(bidResponse.width).to.equal(300);
expect(bidResponse.height).to.equal(250);
expect(bidResponse.statusMessage).to.equal('Bid available');
expect(bidResponse.adId).to.equal('84ab500420319d');
expect(bidResponse.mediaType).to.equal('banner');
expect(bidResponse.creative_id).to.equal('42');
expect(bidResponse.code).to.equal('adxcg');
expect(bidResponse.cpm).to.equal(0.45);
expect(bidResponse.ad).to.equal('<!-- adContent -->');
});

it('handles video responses', () => {
server.respondWith(JSON.stringify(VIDEO_RESPONSE));

adapter.callBids(REQUEST);
server.respond();
sinon.assert.calledOnce(bidmanager.addBidResponse);

const bidResponse = bidmanager.addBidResponse.firstCall.args[1];
expect(bidResponse.bidderCode).to.equal('adxcg');
expect(bidResponse.width).to.equal(640);
expect(bidResponse.height).to.equal(360);
expect(bidResponse.statusMessage).to.equal('Bid available');
expect(bidResponse.adId).to.equal('84ab500420319d');
expect(bidResponse.mediaType).to.equal('video');
expect(bidResponse.creative_id).to.equal('42');
expect(bidResponse.code).to.equal('adxcg');
expect(bidResponse.cpm).to.equal(0.45);
expect(bidResponse.vastUrl).to.equal('vastContentUrl');
expect(bidResponse.descriptionUrl).to.equal('vastContentUrl');
});

it('handles native responses', () => {
server.respondWith(JSON.stringify(NATIVE_RESPONSE));

adapter.callBids(REQUEST);
server.respond();
sinon.assert.calledOnce(bidmanager.addBidResponse);

const bidResponse = bidmanager.addBidResponse.firstCall.args[1];
expect(bidResponse.bidderCode).to.equal('adxcg');
expect(bidResponse.width).to.equal(0);
expect(bidResponse.height).to.equal(0);
expect(bidResponse.statusMessage).to.equal('Bid available');
expect(bidResponse.adId).to.equal('84ab500420319d');
expect(bidResponse.mediaType).to.equal('native');
expect(bidResponse.creative_id).to.equal('42');
expect(bidResponse.code).to.equal('adxcg');
expect(bidResponse.cpm).to.equal(0.45);

expect(bidResponse.native.clickUrl).to.equal('linkContent');
expect(bidResponse.native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']);
expect(bidResponse.native.title).to.equal('titleContent');
expect(bidResponse.native.image).to.equal('imageContent');
expect(bidResponse.native.body).to.equal('descriptionContent');
expect(bidResponse.native.sponsoredBy).to.equal('sponsoredByContent');
});

it('handles nobid responses', () => {
server.respondWith('[]');

adapter.callBids(REQUEST);
server.respond();
sinon.assert.calledOnce(bidmanager.addBidResponse);

const bidResponse = bidmanager.addBidResponse.firstCall.args[1];
expect(bidResponse.statusMessage).to.equal('Bid returned empty or error response');
});
});
});

0 comments on commit a7335be

Please sign in to comment.