Skip to content

Commit

Permalink
Ras Bid Adapter: Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
wsusrasp committed Jun 21, 2022
1 parent 5ece4bb commit 7e8a5dd
Show file tree
Hide file tree
Showing 3 changed files with 357 additions and 0 deletions.
136 changes: 136 additions & 0 deletions modules/rasBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER } from '../src/mediaTypes.js';
import { isEmpty, getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js';

const BIDDER_CODE = 'ras';
const GET_ENDPOINT = (network) => `https://csr.onet.pl/${encodeURIComponent(network)}/csr-006/csr.json?`;
const VERSION = '1.0';

function parseParams(params) {
const newParams = {};
const pageContext = params.pageContext;
if (!pageContext) {
return {};
}
if (pageContext.dr) {
newParams.dr = pageContext.dr
}
if (pageContext.dv) {
newParams.DV = pageContext.dv
}
if (pageContext.keyWords && Array.isArray(pageContext.keyWords)) {
newParams.kwrd = pageContext.keyWords.join('+')
}
if (pageContext.capping) {
newParams.local_capping = pageContext.capping;
}
if (pageContext.keyValues && typeof pageContext.keyValues === 'object') {
for (const param in pageContext.keyValues) {
if (pageContext.keyValues.hasOwnProperty(param)) {
const kvName = 'kv' + param;
newParams[kvName] = pageContext.keyValues[param]
}
}
}
return newParams;
}

const buildBid = (ad) => {
if (ad.type === 'empty') {
return null;
}
return {
requestId: ad.id,
cpm: ad.bid_rate ? ad.bid_rate.toFixed(2) : 0,
width: ad.width || 0,
height: ad.height || 0,
ttl: 300,
creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0,
netRevenue: true,
currency: ad.currency || 'USD',
dealId: null,
meta: {
mediaType: BANNER
},
ad: ad.html || null
};
};

const getContextParams = (bidRequests) => {
const bid = bidRequests[0];
const { params } = bid;
const requestParams = {
nid: encodeURIComponent(params.network),
site: params.site,
area: params.area,
cre_format: 'html',
systems: 'das',
kvprver: VERSION,
ems_url: 1,
bid_rate: 1,
...parseParams(params)
};
return Object.keys(requestParams).map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(requestParams[key])).join('&');
};

const getSlots = (bidRequests) => {
let queryString = '';
const batchSize = bidRequests.length;
for (let i = 0; i < batchSize; i++) {
const adunit = bidRequests[i];
const { slot } = adunit.params;
const sizes = parseSizesInput(getAdUnitSizes(adunit)).join(',');
queryString += `&slot${i}=${encodeURIComponent(slot)}&id${i}=${encodeURIComponent(adunit.bidId)}&composition${i}=CHILD`;
queryString += sizes.length ? `&iusizes${i}=${encodeURIComponent(sizes)}` : ''
}
return queryString;
};

const getGdprParams = (bidderRequest) => {
const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies');
let consentString = deepAccess(bidderRequest, 'gdprConsent.consentString');
let queryString = '';
if (gdprApplies !== undefined) {
queryString += `&gdpr_applies=${encodeURIComponent(gdprApplies)}`;
}
if (consentString !== undefined) {
queryString += `&euconsent=${encodeURIComponent(consentString)}`;
}
return queryString;
};

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

isBidRequestValid: function (bidRequest) {
if (!bidRequest || !bidRequest.params || typeof bidRequest.params !== 'object') {
return;
}
const { params } = bidRequest;
return Boolean(params.network && params.site && params.area && params.slot);
},

buildRequests: function (bidRequests, bidderRequest) {
const slotsQuery = getSlots(bidRequests);
const contextQuery = getContextParams(bidRequests);
const gdprQuery = getGdprParams(bidderRequest);
const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, bidId: bid.bidId }));
const network = bidRequests[0].params.network;
return [{
method: 'GET',
url: GET_ENDPOINT(network) + contextQuery + slotsQuery + gdprQuery,
bidIds: bidIds
}];
},

interpretResponse: function (serverResponse, bidRequest) {
const response = serverResponse.body;
if (!response || !response.ads || response.ads.length === 0) {
return [];
}
return response.ads.map(buildBid).filter((bid) => !isEmpty(bid));
}
};

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

```
Module Name: Ringier Axel Springer Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

Module that connects to Ringer Axel Springer demand sources.
Only banner format is supported.

# Test Parameters
```js
var adUnits = [{
code: 'test-div-ad',
mediaTypes: {
banner: {
sizes: [[300, 250], [300, 600]]
}
},
bids: [{
bidder: 'ras',
params: {
network: '4178463',
site: 'test',
area: 'areatest',
slot: 'slot'
}
}]
}];
```

# Parameters

| Name | Scope | Type | Description | Example
| --- | --- | --- | --- | ---
| network | required | String | Specific identifier provided by RAS | `"4178463"`
| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"`
| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"`
| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"`
| pageContext | optional | Object | Web page context data | `{}`
| pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"`
| pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"`
| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"`
| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]`
| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}`
| pageContext.keyValues.pos | optional | Number | Ad unit position (integer greather than zero) | `1`
| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"`
| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"`
170 changes: 170 additions & 0 deletions test/spec/modules/rasBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { expect } from 'chai';
import { spec } from 'modules/rasBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';

const CSR_ENDPOINT = 'https://csr.onet.pl/1746213/csr-006/csr.json?';

describe('rasBidAdapter', 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 () {
it('should return true when required params found', function () {
const bid = {
sizes: [[300, 250], [300, 600]],
bidder: 'ras',
params: {
slot: 'slot',
area: 'NOWASG',
site: 'GLOWNA',
network: '1746213'
}
};
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when required params not found', function () {
const failBid = {
sizes: [[300, 250], [300, 300]],
bidder: 'ras',
params: {
site: 'GLOWNA',
network: '1746213'
}
};
expect(spec.isBidRequestValid(failBid)).to.equal(false);
});

it('should return nothing when bid request is malformed', function () {
const failBid = {
sizes: [[300, 250], [300, 300]],
bidder: 'ras',
};
expect(spec.isBidRequestValid(failBid)).to.equal(undefined);
});
});

describe('buildRequests', function () {
const bid = {
sizes: [[300, 250], [300, 600]],
bidder: 'ras',
bidId: 1,
params: {
slot: 'test',
area: 'NOWASG',
site: 'GLOWNA',
network: '1746213'
}
};
const bid2 = {
sizes: [[750, 300]],
bidder: 'ras',
bidId: 2,
params: {
slot: 'test2',
area: 'NOWASG',
site: 'GLOWNA',
network: '1746213'
}
};

it('should parse bids to request', function () {
const requests = spec.buildRequests([bid], {
'gdprConsent': {
'gdprApplies': true,
'consentString': 'some-consent-string'
}
});
expect(requests[0].url).to.have.string(CSR_ENDPOINT);
expect(requests[0].url).to.have.string('slot0=test');
expect(requests[0].url).to.have.string('id0=1');
expect(requests[0].url).to.have.string('nid=1746213');
expect(requests[0].url).to.have.string('site=GLOWNA');
expect(requests[0].url).to.have.string('area=NOWASG');
expect(requests[0].url).to.have.string('cre_format=html');
expect(requests[0].url).to.have.string('systems=das');
expect(requests[0].url).to.have.string('ems_url=1');
expect(requests[0].url).to.have.string('bid_rate=1');
expect(requests[0].url).to.have.string('gdpr_applies=true');
expect(requests[0].url).to.have.string('euconsent=some-consent-string');
});
it('should return empty consent string when undefined', function () {
const requests = spec.buildRequests([bid]);
const gdpr = requests[0].url.search('gdpr_applies');
const euconsent = requests[0].url.search('euconsent=');
expect(gdpr).to.equal(-1);
expect(euconsent).to.equal(-1);
});
it('should parse bids to request from pageContext', function () {
const bidCopy = { ...bid };
bidCopy.params = { ...bid.params, pageContext: { 'dr': 'test.pl', keyValues: { seg_ab: 10 } } };
const requests = spec.buildRequests([bidCopy, bid2]);
expect(requests[0].url).to.have.string(CSR_ENDPOINT);
expect(requests[0].url).to.have.string('slot0=test');
expect(requests[0].url).to.have.string('slot0=test');
expect(requests[0].url).to.have.string('id0=1');
expect(requests[0].url).to.have.string('iusizes0=300x250%2C300x600');
expect(requests[0].url).to.have.string('slot1=test2');
expect(requests[0].url).to.have.string('id1=2');
expect(requests[0].url).to.have.string('iusizes1=750x300');
expect(requests[0].url).to.have.string('nid=1746213');
expect(requests[0].url).to.have.string('site=GLOWNA');
expect(requests[0].url).to.have.string('area=NOWASG');
expect(requests[0].url).to.have.string('cre_format=html');
expect(requests[0].url).to.have.string('systems=das');
expect(requests[0].url).to.have.string('ems_url=1');
expect(requests[0].url).to.have.string('bid_rate=1');
expect(requests[0].url).to.have.string('dr=test.pl');
});
});

describe('interpretResponse', function () {
const response = {
'adsCheck': 'ok',
'geoloc': {},
'ir': '92effd60-0c84-4dac-817e-763ea7b8ac65',
'ads': [
{
'id': 'flat-belkagorna',
'slot': 'flat-belkagorna',
'prio': 10,
'type': 'html',
'bid_rate': 0.321123,
'adid': 'das,50463,152276',
'id_3': '12734',
'html': '<script type=\"text/javascript\">test</script>'
}
],
'iv': '202003191334467636346500'
};

it('should get correct bid response', function () {
const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'flat-belkagorna', bidId: 1 }] });
expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'ad', 'meta');
expect(resp.length).to.equal(1);
});

it('should handle empty ad', function () {
let res = {
'ads': [{
type: 'empty'
}]
};
const resp = spec.interpretResponse({ body: res }, {});
expect(resp).to.deep.equal([]);
});

it('should handle empty server response', function () {
let res = {
'ads': []
};
const resp = spec.interpretResponse({ body: res }, {});
expect(resp).to.deep.equal([]);
});
});
});

0 comments on commit 7e8a5dd

Please sign in to comment.