Skip to content

Commit

Permalink
Adnow bidder (#5738)
Browse files Browse the repository at this point in the history
* Add AdNow bid Adaptor

* Fix problems by PR comments.

* PR comments:
- Use only secure endpoint.
- Use adUnit mediaTypes instead of mediaType param in buildRequests.
- Pass correct sizes to the endpoint for banner and native.
- Fix adnowBidAdaper.md examples.
- Fix and add new tests in adnowBidAdaper_spec.js

* rename test

* Restore package-lock.json from master

* Fix sizes of bid response object for banners.

* Fix adapters tests.
  • Loading branch information
vingood authored Sep 25, 2020
1 parent 05a5a96 commit ecd05a3
Show file tree
Hide file tree
Showing 3 changed files with 387 additions and 0 deletions.
178 changes: 178 additions & 0 deletions modules/adnowBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { NATIVE, BANNER } from '../src/mediaTypes.js';
import * as utils from '../src/utils.js';
import includes from 'core-js-pure/features/array/includes.js';

const BIDDER_CODE = 'adnow';
const ENDPOINT = 'https://n.ads3-adnow.com/a';

/**
* @typedef {object} CommonBidData
*
* @property {string} requestId The specific BidRequest which this bid is aimed at.
* This should match the BidRequest.bidId which this Bid targets.
* @property {string} currency The currency code for the cpm value
* @property {number} cpm The bid price, in US cents per thousand impressions.
* @property {string} creativeId The id of ad content
* @property {number} ttl Time-to-live - how long (in seconds) Prebid can use this bid.
* @property {boolean} netRevenue Boolean defining whether the bid is Net or Gross. The default is true (Net).
* @property {object} [meta] Object for storing bid meta data
* @property {string} [meta.mediaType] banner or native
*/

/** @type {BidderSpec} */
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [ NATIVE, BANNER ],

/**
* @param {object} bid
* @return {boolean}
*/
isBidRequestValid(bid) {
if (!bid || !bid.params) return false;

const codeId = parseInt(bid.params.codeId, 10);
if (!codeId) {
return false;
}

const mediaType = bid.params.mediaType || NATIVE;

return includes(this.supportedMediaTypes, mediaType);
},

/**
* @param {BidRequest[]} validBidRequests
* @param {*} bidderRequest
* @return {ServerRequest}
*/
buildRequests(validBidRequests, bidderRequest) {
return validBidRequests.map(req => {
const mediaType = this._isBannerRequest(req) ? BANNER : NATIVE;
const codeId = parseInt(req.params.codeId, 10);

const data = {
Id: codeId,
mediaType: mediaType,
out: 'prebid',
d_user_agent: navigator.userAgent,
requestid: req.bidId
};

if (mediaType === BANNER) {
data.sizes = utils.parseSizesInput(
req.mediaTypes && req.mediaTypes.banner && req.mediaTypes.banner.sizes
).join('|')
} else {
data.width = data.height = 200;

let sizes = utils.deepAccess(req, 'mediaTypes.native.image.sizes', []);

if (sizes.length > 0) {
const size = Array.isArray(sizes[0]) ? sizes[0] : sizes;

data.width = size[0] || data.width;
data.height = size[1] || data.height;
}
}

/** @type {ServerRequest} */
return {
method: 'GET',
url: ENDPOINT,
data: utils.parseQueryStringParameters(data),
options: {
withCredentials: false,
crossOrigin: true
},
bidRequest: req
};
});
},

/**
* @param {*} response
* @param {ServerRequest} request
* @return {Bid[]}
*/
interpretResponse(response, request) {
const bidObj = request.bidRequest;
let bid = response.body;

if (!bid || !bid.currency || !bid.cpm) {
return [];
}

const mediaType = bid.meta.mediaType || NATIVE;
if (!includes(this.supportedMediaTypes, mediaType)) {
return [];
}

bid.requestId = bidObj.bidId;

if (mediaType === BANNER) {
return [ this._getBannerBid(bid) ];
}

if (mediaType === NATIVE) {
return [ this._getNativeBid(bid) ];
}

return [];
},

/**
* @private
* @param {object} bid
* @return {CommonBidData}
*/
_commonBidData(bid) {
return {
requestId: bid.requestId,
currency: bid.currency || 'USD',
cpm: bid.cpm || 0.00,
creativeId: bid.creativeId || 'undefined-creative',
netRevenue: bid.netRevenue || true,
ttl: bid.ttl || 360,
meta: bid.meta || {}
};
},

/**
* @param {BidRequest} req
* @return {boolean}
* @private
*/
_isBannerRequest(req) {
return !!(req.mediaTypes && req.mediaTypes.banner);
},

/**
* @private
* @param {object} bid
* @return {Bid}
*/
_getBannerBid(bid) {
return {
...this._commonBidData(bid),
width: bid.width || 300,
height: bid.height || 250,
ad: bid.ad || '<div>Empty Ad</div>'
};
},

/**
* @private
* @param {object} bid
* @return {Bid}
*/
_getNativeBid(bid) {
return {
...this._commonBidData(bid),
native: bid.native || {}
};
}
}

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

```
Module Name: AdNow Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

AdNow Bidder Adapter for Prebid.js.
Banner and Native format are supported.
Please use ```adnow``` as the bidder code.

# Test Parameters
```javascript
const adUnits = [{
code: 'test',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
bids: [{
bidder: 'adnow',
params: {
codeId: 794934
}
}]
}, {
code: 'test',
mediaTypes: {
native: {
image: {
sizes: [200, 200]
}
}
},
bids: [{
bidder: 'adnow',
params: {
codeId: 794934
}
}]
}];
```
163 changes: 163 additions & 0 deletions test/spec/modules/adnowBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { expect } from 'chai';
import { spec } from 'modules/adnowBidAdapter.js';

describe('adnowBidAdapter', function () {
describe('isBidRequestValid', function () {
it('Should return true', function() {
expect(spec.isBidRequestValid({
bidder: 'adnow',
params: {
codeId: 12345
}
})).to.equal(true);
});

it('Should return false when required params is not passed', function() {
expect(spec.isBidRequestValid({
bidder: 'adnow',
params: {}
})).to.equal(false);
});
});

describe('buildRequests', function() {
it('Common settings', function() {
const bidRequestData = [{
bidId: 'bid12345',
params: {
codeId: 12345
}
}];

const req = spec.buildRequests(bidRequestData);
const reqData = req[0].data;

expect(reqData)
.to.match(/Id=12345/)
.to.match(/mediaType=native/)
.to.match(/out=prebid/)
.to.match(/requestid=bid12345/)
.to.match(/d_user_agent=.+/);
});

it('Banner sizes', function () {
const bidRequestData = [{
bidId: 'bid12345',
params: {
codeId: 12345
},
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
}
}];

const req = spec.buildRequests(bidRequestData);
const reqData = req[0].data;

expect(reqData).to.match(/sizes=300x250/);
});

it('Native sizes', function () {
const bidRequestData = [{
bidId: 'bid12345',
params: {
codeId: 12345
},
mediaTypes: {
native: {
image: {
sizes: [100, 100]
}
}
}
}];

const req = spec.buildRequests(bidRequestData);
const reqData = req[0].data;

expect(reqData)
.to.match(/width=100/)
.to.match(/height=100/);
});
});

describe('interpretResponse', function() {
const request = {
bidRequest: {
bidId: 'bid12345'
}
};

it('Response with native bid', function() {
const response = {
currency: 'USD',
cpm: 0.5,
native: {
title: 'Title',
body: 'Body',
sponsoredBy: 'AdNow',
clickUrl: '//click.url',
image: {
url: '//img.url',
height: 200,
width: 200
}
},
meta: {
mediaType: 'native'
}
};

const bids = spec.interpretResponse({ body: response }, request);
expect(bids).to.be.an('array').that.is.not.empty;

const bid = bids[0];
expect(bid).to.have.keys('requestId', 'cpm', 'currency', 'native', 'creativeId', 'netRevenue', 'meta', 'ttl');

const nativePart = bid.native;

expect(nativePart.title).to.be.equal('Title');
expect(nativePart.body).to.be.equal('Body');
expect(nativePart.clickUrl).to.be.equal('//click.url');
expect(nativePart.image.url).to.be.equal('//img.url');
expect(nativePart.image.height).to.be.equal(200);
expect(nativePart.image.width).to.be.equal(200);
});

it('Response with banner bid', function() {
const response = {
currency: 'USD',
cpm: 0.5,
ad: '<div>Banner</div>',
meta: {
mediaType: 'banner'
}
};

const bids = spec.interpretResponse({ body: response }, request);
expect(bids).to.be.an('array').that.is.not.empty;

const bid = bids[0];
expect(bid).to.have.keys(
'requestId', 'cpm', 'currency', 'ad', 'creativeId', 'netRevenue', 'meta', 'ttl', 'width', 'height'
);

expect(bid.ad).to.be.equal('<div>Banner</div>');
});

it('Response with no bid should return an empty array', function() {
const noBidResponses = [
false,
{},
{body: false},
{body: {}}
];

noBidResponses.forEach(response => {
return expect(spec.interpretResponse(response, request)).to.be.an('array').that.is.empty;
});
});
});
});

0 comments on commit ecd05a3

Please sign in to comment.