Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AdDefend Bid Adapter: new bid adapter #6450

Merged
2 commits merged into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions modules/addefendBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';

const BIDDER_CODE = 'addefend';

export const spec = {
code: BIDDER_CODE,
hostname: 'https://addefend-platform.com',

getHostname() {
return this.hostname;
},
isBidRequestValid: function(bid) {
return (bid.sizes !== undefined && bid.bidId !== undefined && bid.params !== undefined &&
(bid.params.pageId !== undefined && (typeof bid.params.pageId === 'string')) &&
(bid.params.placementId !== undefined && (typeof bid.params.placementId === 'string')));
},
buildRequests: function(validBidRequests, bidderRequest) {
let bid = {
v: $$PREBID_GLOBAL$$.version,
auctionId: false,
pageId: false,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these supposed to be bools?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, for the theoretical case that there are no validBidRequests (length==0) the resulting request would send false for auctionId and pageId.

gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '',
referer: bidderRequest.refererInfo.referer,
bids: [],
};

for (var i = 0; i < validBidRequests.length; i++) {
let vb = validBidRequests[i];
let o = vb.params;
bid.auctionId = vb.auctionId;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this loop does it mean that bid.blah will get overridden every time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, auctionId, pageId and trafficTypes are supposed to be the same for all the requests.

o.bidId = vb.bidId;
o.transactionId = vb.transactionId;
o.sizes = [];
if (o.trafficTypes) {
bid.trafficTypes = o.trafficTypes;
}
delete o.trafficTypes;

bid.pageId = o.pageId;
delete o.pageId;

if (vb.sizes && Array.isArray(vb.sizes)) {
for (var j = 0; j < vb.sizes.length; j++) {
let s = vb.sizes[j];
if (Array.isArray(s) && s.length == 2) {
o.sizes.push(s[0] + 'x' + s[1]);
}
}
}
bid.bids.push(o);
}
return [{
method: 'POST',
url: this.getHostname() + '/bid',
options: { withCredentials: true },
data: bid
}];
},
interpretResponse: function(serverResponse, request) {
const requiredKeys = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'advertiserDomains'];
const validBidResponses = [];
serverResponse = serverResponse.body;
if (serverResponse && (serverResponse.length > 0)) {
serverResponse.forEach((bid) => {
const bidResponse = {};
for (const requiredKey of requiredKeys) {
if (!bid.hasOwnProperty(requiredKey)) {
return [];
}
bidResponse[requiredKey] = bid[requiredKey];
}
validBidResponses.push(bidResponse);
});
}
return validBidResponses;
}
}

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

```
Module Name: AdDefend Bid Adapter
Module Type: Bidder Adapter
Maintainer: prebid@addefend.com
```

# Description

Module that connects to AdDefend as a demand source.

## Parameters
| Param | Description | Optional | Default |
| ------------- | ------------- | ----- | ----- |
| pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - |
| placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - |
| trafficTypes | comma seperated list of the following traffic types:<br/>ADBLOCK - user has a activated adblocker<br/>PM - user has firefox private mode activated<br/>NC - user has not given consent<br/>NONE - user traffic is none of the above, this usually means this is a "normal" user.<br/>| yes | ADBLOCK |


# Test Parameters
```
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[970, 250]], // a display size
}
},
bids: [
{
bidder: "addefend",
params: {
pageId: "887",
placementId: "9398",
trafficTypes: "ADBLOCK"
}
}
]
}
];
```
184 changes: 184 additions & 0 deletions test/spec/modules/addefendBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {expect} from 'chai';
import {spec} from 'modules/addefendBidAdapter.js';

describe('addefendBidAdapter', () => {
const defaultBidRequest = {
bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb',
auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8',
transactionId: 'd58851660c0c4461e4aa06344fc9c0c6',
sizes: [[300, 250], [300, 600]],
params: {
pageId: 'stringid1',
placementId: 'stringid2'
}
};

const deepClone = function (val) {
return JSON.parse(JSON.stringify(val));
};

const buildRequest = (buildRequest, bidderRequest) => {
if (!Array.isArray(buildRequest)) {
buildRequest = [buildRequest];
}

return spec.buildRequests(buildRequest, {
...bidderRequest || {},
refererInfo: {
referer: 'https://referer.example.com'
}
})[0];
};

describe('isBidRequestValid', () => {
it('should return true when required params found', () => {
const bidRequest = deepClone(defaultBidRequest);
expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
});

it('pageId performs type checking', () => {
const bidRequest = deepClone(defaultBidRequest);
bidRequest.params.pageId = 1; // supposed to be a string
expect(spec.isBidRequestValid(bidRequest)).to.equal(false);
});

it('placementId performs type checking', () => {
const bidRequest = deepClone(defaultBidRequest);
bidRequest.params.placementId = 1; // supposed to be a string
expect(spec.isBidRequestValid(bidRequest)).to.equal(false);
});

it('should return false when required params are not passed', () => {
const bidRequest = deepClone(defaultBidRequest);
delete bidRequest.params;
expect(spec.isBidRequestValid(bidRequest)).to.equal(false);
});
});

describe('buildRequests', () => {
const bidRequest = deepClone(defaultBidRequest);
const request = buildRequest(bidRequest);

it('sends bid request to endpoint via https using post', () => {
expect(request.method).to.equal('POST');
expect(request.url.indexOf('https://')).to.equal(0);
expect(request.url).to.equal(`${spec.hostname}/bid`);
});

it('contains prebid version parameter', () => {
expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version);
});

it('contains correct referer', () => {
expect(request.data.referer).to.equal('https://referer.example.com');
});

it('contains auctionId', () => {
expect(request.data.auctionId).to.equal('ccc4c7cdfe11cfbd74065e6dd28413d8');
});

it('contains pageId', () => {
expect(request.data.pageId).to.equal('stringid1');
});

it('sends correct bid parameters', () => {
const bidRequest = deepClone(defaultBidRequest);
expect(request.data.bids).to.deep.equal([ {
bidId: bidRequest.bidId,
placementId: bidRequest.params.placementId,
sizes: [ '300x250', '300x600' ],
transactionId: 'd58851660c0c4461e4aa06344fc9c0c6'
} ]);
});

it('handles empty gdpr object', () => {
const bidRequest = deepClone(defaultBidRequest);
const request = buildRequest(bidRequest, {
gdprConsent: {}
});
expect(request.data.gdpr_consent).to.be.equal('');
});

it('handles non-existent gdpr object', () => {
const bidRequest = deepClone(defaultBidRequest);
const request = buildRequest(bidRequest, {
gdprConsent: null
});
expect(request.data.gdpr_consent).to.be.equal('');
});

it('handles properly filled gdpr string', () => {
const bidRequest = deepClone(defaultBidRequest);
const consentString = 'GDPR_CONSENT_STRING';
const request = buildRequest(bidRequest, {
gdprConsent: {
gdprApplies: true,
consentString: consentString
}
});

expect(request.data.gdpr_consent).to.be.equal(consentString);
});
});

describe('interpretResponse', () => {
it('should get correct bid response', () => {
const serverResponse = [
{
'width': 300,
'height': 250,
'creativeId': '29681110',
'ad': '<!-- Creative -->',
'cpm': 0.5,
'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8',
'ttl': 120,
'netRevenue': true,
'currency': 'EUR',
'advertiserDomains': ['advertiser.example.com']
}
];

const expectedResponse = [
{
'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8',
'cpm': 0.5,
'creativeId': '29681110',
'width': 300,
'height': 250,
'ttl': 120,
'currency': 'EUR',
'ad': '<!-- Creative -->',
'netRevenue': true,
'advertiserDomains': ['advertiser.example.com']
}
];

const result = spec.interpretResponse({body: serverResponse});
expect(result.length).to.equal(expectedResponse.length);
Object.keys(expectedResponse[0]).forEach((key) => {
expect(result[0][key]).to.deep.equal(expectedResponse[0][key]);
});
});

it('handles incomplete server response', () => {
const serverResponse = [
{
'ad': '<!-- Creative -->',
'cpm': 0.5,
'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8',
'ttl': 60
}
];
const result = spec.interpretResponse({body: serverResponse});

expect(result.length).to.equal(0);
});

it('handles nobid responses', () => {
const serverResponse = [];
const result = spec.interpretResponse({body: serverResponse});

expect(result.length).to.equal(0);
});
});
});