Skip to content

Commit

Permalink
Add a new adapter for Appier bidder.
Browse files Browse the repository at this point in the history
  • Loading branch information
PCMan-Appier authored and PCMan committed Dec 13, 2018
1 parent 88156e7 commit 28f538b
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 0 deletions.
89 changes: 89 additions & 0 deletions modules/appierBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { registerBidder } from 'src/adapters/bidderFactory';
import { BANNER } from 'src/mediaTypes';
import { config } from 'src/config';

export const ADAPTER_VERSION = '1.0.0';
const SUPPORTED_AD_TYPES = [BANNER];

// we have different servers for different regions / farms
export const API_SERVERS_MAP = {
'default': 'ad2.apx.appier.net',
'tw': 'ad2.apx.appier.net',
'jp': 'ad-jp.apx.appier.net'
};

const BIDDER_API_ENDPOINT = '/v1/prebid/bid';

export const spec = {
code: 'appier',
supportedMediaTypes: SUPPORTED_AD_TYPES,

/**
* Determines whether or not the given bid request is valid.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
return typeof bid.params.hzid === 'string';
},

/**
* Make a server request from the list of BidRequests.
*
* @param {bidRequests[]} - an array of bids
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (bidRequests, bidderRequest) {
if (bidRequests.length === 0) {
return [];
}
const server = this.getApiServer();
const bidderApiUrl = `//${server}${BIDDER_API_ENDPOINT}`
const payload = {
'bids': bidRequests,
'refererInfo': bidderRequest.refererInfo,
'version': ADAPTER_VERSION
};
return [{
method: 'POST',
url: bidderApiUrl,
data: payload,
// keep the bidder request object for later use
bidderRequest: bidderRequest
}];
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {serverResponse} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function (serverResponse, serverRequest) {
if (!Array.isArray(serverResponse.body)) {
return [];
}
// server response body is an array of bid results
const bidResults = serverResponse.body;
// our server directly returns the format needed by prebid.js so no more
// transformation is needed here.
return bidResults;
},

/**
* Get the hostname of the server we want to use.
*/
getApiServer() {
// we may use different servers for different farms (geographical regions)
// if a server is specified explicitly, use it. otherwise, use farm specific server.
let server = config.getConfig('appier.server');
if (!server) {
const farm = config.getConfig('appier.farm');
server = API_SERVERS_MAP[farm] || API_SERVERS_MAP['default'];
}
return server;
}
};

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

```
Module Name: Appier Bid Adapter
Module Type: Bidder Adapter
Maintainer: apn-dev@appier.com
```

# Description

Connects to Appier exchange for bids.

NOTE:
- Appier bid adapter only supports Banner at the moment.
- Multi-currency is not supported. Please make sure you have correct DFP currency settings according to your deal with Appier.

# Sample Ad Unit Config
```
var adUnits = [
// Banner adUnit
{
code: 'banner-div',
mediaTypes: {
banner: {
sizes: [[300, 250], [300,600]]
}
},
bids: [{
bidder: 'appier',
params: {
hzid: 'WhM5WIOp'
}
}]
}
];
```

# Additional Config (Optional)
Set the "farm" to use region-specific server
```
// use the bid server in Taiwan (country code: tw)
pbjs.setConfig({
appier: {
'farm': 'tw'
}
});
```

Explicitly override the bid server used for bidding
```
// use the bid server specified and override the default
pbjs.setConfig({
appier: {
'server': '${HOST_NAME_OF_THE_SERVER}'
}
});
```
174 changes: 174 additions & 0 deletions test/spec/modules/appierBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { expect } from 'chai';
import { spec, API_SERVERS_MAP, ADAPTER_VERSION } from 'modules/appierBidAdapter';
import { newBidder } from 'src/adapters/bidderFactory';
import { config } from 'src/config';

describe('AppierAdapter', 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': 'appier',
'params': {
'hzid': 'abcd'
},
'adUnitCode': 'adunit-code',
'sizes': [[300, 250], [300, 600]],
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
};

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

it('should return false when required param zoneId is missing', function () {
let bid = Object.assign({}, bid);
bid.params = {};
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false when required param zoneId has wrong type', function () {
let bid = Object.assign({}, bid);
bid.params = {
'hzid': null
};
expect(spec.isBidRequestValid(bid)).to.equal(false);
});
});

describe('buildRequests', function() {
it('should return an empty list when there are no bid requests', function() {
const fakeBidRequests = [];
const fakeBidderRequest = {};
expect(spec.buildRequests(fakeBidRequests, fakeBidderRequest)).to.be.an('array').that.is.empty;
});

it('should generate a POST bid request with method, url, and data fields', function() {
const bid = {
'bidder': 'appier',
'params': {
'hzid': 'abcd'
},
'adUnitCode': 'adunit-code',
'sizes': [[300, 250], [300, 600]],
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
};
const fakeBidRequests = [bid];
const fakeBidderRequest = {refererInfo: {
'referer': 'fakeReferer',
'reachedTop': true,
'numIframes': 1,
'stack': []
}};

const builtRequests = spec.buildRequests(fakeBidRequests, fakeBidderRequest);
expect(builtRequests.length).to.equal(1);
expect(builtRequests[0].method).to.equal('POST');
expect(builtRequests[0].url).match(/v1\/prebid\/bid/);
expect(builtRequests[0].data).deep.equal({
'bids': fakeBidRequests,
'refererInfo': fakeBidderRequest.refererInfo,
'version': ADAPTER_VERSION
});
});
});

describe('interpretResponse', function() {
const bid = {
'bidder': 'appier',
'params': {
'hzid': 'abcd'
},
'adUnitCode': 'adunit-code',
'sizes': [[300, 250], [300, 600]],
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
};
const fakeBidRequests = [bid];

it('should return an empty aray to indicate no valid bids', function() {
const fakeServerResponse = {};

const bidResponses = spec.interpretResponse(fakeServerResponse, fakeBidRequests);

expect(bidResponses).is.an('array').that.is.empty;
});

it('should generate correct response array for bidder', function() {
const fakeBidResult = {
'requestId': '30b31c1838de1e',
'cpm': 0.0029346001,
'creativeId': 'Idl0P0d5S3Ca5kVWcia-wQ',
'width': 300,
'height': 250,
'currency': 'USD',
'netRevenue': true,
'ttl': 300,
'ad': '<div>fake html</div>',
'appierParams': {
'hzid': 'test_hzid'
}
};
const fakeServerResponse = {
headers: [],
body: [fakeBidResult]
};

const bidResponses = spec.interpretResponse(fakeServerResponse, fakeBidRequests);
expect(bidResponses).deep.equal([fakeBidResult]);
});
});

describe('getApiServer', function() {
it('should use the server specified by setConfig(appier.server)', function() {
config.setConfig({
'appier': {'server': 'fake_server'}
});

const server = spec.getApiServer();

expect(server).equals('fake_server');
});

it('should retrieve a farm specific hostname if server is not specpfied', function() {
config.setConfig({
'appier': {'farm': 'tw'}
});

const server = spec.getApiServer();

expect(server).equals(API_SERVERS_MAP['tw']);
});

it('if farm is not recognized, use the default farm', function() {
config.setConfig({
'appier': {'farm': 'no_this_farm'}
});

const server = spec.getApiServer();

expect(server).equals(API_SERVERS_MAP['default']);
});

it('if farm is not specified, use the default farm', function() {
config.setConfig({
'appier': {}
});

const server = spec.getApiServer();

expect(server).equals(API_SERVERS_MAP['default']);
});
});
});

0 comments on commit 28f538b

Please sign in to comment.