Skip to content

Commit

Permalink
Audiencerun bid adapter (#4761)
Browse files Browse the repository at this point in the history
* New adapter AudienceRun

* Fix issues AudiencerunAdapter

* Fix issues AudiencerunAdapter

* Fix issues AudiencerunAdapter

* Fix issues AudiencerunAdapter
  • Loading branch information
audiencerun authored Jan 31, 2020
1 parent 9c08696 commit 613b99b
Show file tree
Hide file tree
Showing 3 changed files with 394 additions and 0 deletions.
142 changes: 142 additions & 0 deletions modules/audiencerunBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import * as utils from '../src/utils';
import { config } from '../src/config';
import { registerBidder } from '../src/adapters/bidderFactory';
import { BANNER } from '../src/mediaTypes';

const BIDDER_CODE = 'audiencerun';
const ENDPOINT_URL = 'https://d.audiencerun.com/prebid';

export const spec = {
version: '1.0.0',
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],

/**
* Determines whether or not the given bid request is valid.
*
* @param {object} bid The bid to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
let isValid = true;
if (!utils.deepAccess(bid, 'params.zoneId')) {
utils.logError('AudienceRun zoneId parameter is required. Bid aborted.');
isValid = false;
}
return isValid;
},

/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
* @param {*} bidderRequest
* @return {ServerRequest} Info describing the request to the server.
*/
buildRequests: function(bidRequests, bidderRequest) {
const bids = bidRequests.map(bid => {
const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes', []);
return {
zoneId: utils.getValue(bid.params, 'zoneId'),
sizes: sizes.map(size => ({
w: size[0],
h: size[1]
})),
bidfloor: bid.params.bidfloor || 0.0,
bidId: bid.bidId,
bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid),
adUnitCode: utils.getBidIdParameter('adUnitCode', bid),
auctionId: utils.getBidIdParameter('auctionId', bid),
transactionId: utils.getBidIdParameter('transactionId', bid)
};
});

const payload = {
libVersion: this.version,
referer: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null,
currencyCode: config.getConfig('currency.adServerCurrency'),
timeout: config.getConfig('bidderTimeout'),
bids
};

if (bidderRequest && bidderRequest.gdprConsent) {
payload.gdpr = {
consent: bidderRequest.gdprConsent.consentString,
applies: bidderRequest.gdprConsent.gdprApplies
};
} else {
payload.gdpr = {
consent: ''
}
}

return {
method: 'POST',
url: ENDPOINT_URL,
data: JSON.stringify(payload),
options: {
withCredentials: true
}
};
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function (serverResponse, bidRequest) {
const bids = [];
utils._each(serverResponse.body.bid, function (bidObject) {
if (!bidObject.cpm || bidObject.cpm === null || !bidObject.adm) {
return;
}

const bid = {};

bid.ad = bidObject.adm;
bid.mediaType = BANNER;

// Common properties
bid.requestId = bidObject.bidId;
bid.adId = bidObject.zoneId;
bid.cpm = parseFloat(bidObject.cpm);
bid.creativeId = bidObject.crid;
bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD';

bid.height = bidObject.h;
bid.width = bidObject.w;
bid.netRevenue = bidObject.isNet ? bidObject.isNet : false;
bid.ttl = 300;

bids.push(bid);
});
return bids;
},

/**
* Register the user sync pixels which should be dropped after the auction.
*
* @param {SyncOptions} syncOptions Which user syncs are allowed?
* @param {ServerResponse[]} serverResponses List of server's responses.
* @return {UserSync[]} The user syncs which should be dropped.
*/
getUserSyncs: function(syncOptions, serverResponses) {
if (!serverResponses || !serverResponses.length) return [];

const syncs = [];
serverResponses.forEach(response => {
response.body.bid.forEach(bidObject => {
syncs.push({
type: 'iframe',
url: bidObject.syncUrl
});
});
});

return syncs;
}
};

registerBidder(spec);
48 changes: 48 additions & 0 deletions modules/audiencerunBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Overview

**Module Name**: AudienceRun Bidder Adapter
**Module Type**: Bidder Adapter
**Maintainer**: prebid@audiencerun.com

# Description

Module that connects to AudienceRun demand sources

Use `audiencerun` as bidder.

`zoneId` is required and must be 10 alphanumeric characters.

## AdUnits configuration example
```
var adUnits = [{
code: 'ad-slot-300x600',
mediaTypes: {
banner: {
sizes: [
[300, 600]
],
}
},
bids: [{
bidder: 'audiencerun',
params: {
zoneId: 'xtov2mgij0'
}
}]
},{
code: 'ad-slot-728x90',
mediaTypes: {
banner: {
sizes: [
[728, 90]
],
}
},
bids: [{
bidder: 'audiencerun',
params: {
zoneId: 'u4q6z6u97b'
}
}]
}];
```
204 changes: 204 additions & 0 deletions test/spec/modules/audiencerunBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { expect } from 'chai';
import { spec } from 'modules/audiencerunBidAdapter';
import { newBidder } from 'src/adapters/bidderFactory';

const ENDPOINT = 'https://d.audiencerun.com/prebid';

const BID_SERVER_RESPONSE = {
body: {
bid: [
{
'bidId': '51ef8751f9aead',
'zoneId': '12345abcde',
'adId': '1234',
'crid': '5678',
'cpm': 8.021951999999999999,
'currency': 'USD',
'w': 728,
'h': 90,
'isNet': false,
'buying_type': 'rtb',
'syncUrl': 'https://ac.audiencerun.com/f/sync.html',
'adm': '<!-- test creative -->'
}
]
}
};

describe('AudienceRun bid adapter tests', function() {
const adapter = newBidder(spec);

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

describe('isBidRequestValid', function() {
let bid = {
'bidder': 'audiencerun',
'params': {
'zoneId': '12345abcde'
},
'adUnitCode': 'adunit-code',
'mediaTypes': {
'banner': {
'sizes': [[300, 250], [300, 600]]
}
},
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
'creativeId': 'er2ee'
};

it('should return true when required params found', function() {
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return true when zoneId is valid', function() {
let bid = Object.assign({}, bid);
delete bid.params;
bid.params = {
'zoneId': '12345abcde'
};

expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when required params are not passed', function() {
let bid = Object.assign({}, bid);
delete bid.params;

bid.params = {};

expect(spec.isBidRequestValid(bid)).to.equal(false);
});
});

describe('buildRequests', function() {
let bidRequests = [
{
'bidder': 'audiencerun',
'bidId': '51ef8751f9aead',
'params': {
'zoneId': '12345abcde'
},
'adUnitCode': 'div-gpt-ad-1460505748561-0',
'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec',
'mediaTypes': {
'banner': {
'sizes': [[320, 50], [300, 250], [300, 600]]
}
},
'bidderRequestId': '418b37f85e772c',
'auctionId': '18fd8b8b0bd757',
'bidRequestsCount': 1
}
];

it('sends a valid bid request to ENDPOINT via POST', function() {
const request = spec.buildRequests(bidRequests, {
gdprConsent: {
consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA',
gdprApplies: true
},
refererInfo: {
canonicalUrl: 'https://example.com/canonical',
referer: 'https://example.com'
}
});

expect(request.url).to.equal(ENDPOINT);
expect(request.method).to.equal('POST');

const payload = JSON.parse(request.data);
expect(payload.gdpr).to.exist;

expect(payload.bids).to.exist.and.to.be.an('array').and.to.have.lengthOf(1);
expect(payload.referer).to.exist;

const bid = payload.bids[0];
expect(bid).to.exist;
expect(bid).to.have.property('bidId');
expect(bid).to.have.property('zoneId');
expect(bid).to.have.property('sizes');
expect(bid.sizes[0].w).to.be.a('number');
expect(bid.sizes[0].h).to.be.a('number');
});

it('should send GDPR to endpoint and honor gdprApplies value', function() {
let consentString = 'bogusConsent';
let bidderRequest = {
'gdprConsent': {
'consentString': consentString,
'gdprApplies': true
}
};

const request = spec.buildRequests(bidRequests, bidderRequest);
const payload = JSON.parse(request.data);
expect(payload.gdpr).to.exist;
expect(payload.gdpr.consent).to.equal(consentString);
expect(payload.gdpr.applies).to.equal(true);

let bidderRequest2 = {
'gdprConsent': {
'consentString': consentString,
'gdprApplies': false
}
};

const request2 = spec.buildRequests(bidRequests, bidderRequest2);
const payload2 = JSON.parse(request2.data);

expect(payload2.gdpr).to.exist;
expect(payload2.gdpr.consent).to.equal(consentString);
expect(payload2.gdpr.applies).to.equal(false);
});
});

describe('interpretResponse', function () {
const expectedResponse = [{
'requestId': '51ef8751f9aead',
'adId': '12345abcde',
'cpm': 8.021951999999999999,
'width': '728',
'height': '90',
'creativeId': '5678',
'currency': 'USD',
'netRevenue': false,
'ttl': 300,
'ad': '<!-- test creative -->',
'mediaType': 'banner'
}];

it('should get the correct bid response by display ad', function () {
let result = spec.interpretResponse(BID_SERVER_RESPONSE);
expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
});

it('handles empty bid response', function () {
const response = {
body: {}
};
let result = spec.interpretResponse(response);
expect(result.length).to.equal(0);
});
});

describe('getUserSyncs', function () {
const serverResponses = [ BID_SERVER_RESPONSE ];
const syncOptions = { iframeEnabled: true };

it('should return empty if no server responses', function() {
const syncs = spec.getUserSyncs(syncOptions, []);
expect(syncs).to.deep.equal([])
});

it('should return user syncs', function () {
const syncs = spec.getUserSyncs(syncOptions, serverResponses);
expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://ac.audiencerun.com/f/sync.html'}])
});
});
});

0 comments on commit 613b99b

Please sign in to comment.